changeset 2655:33c76ab5924a

Merge branch 'master' of https://github.com/BrechtDeMan/WebAudioEvaluationTool
author www-data <www-data@sucuk.dcs.qmul.ac.uk>
date Mon, 13 Feb 2017 12:20:48 +0000
parents f5d8a0af942f (current diff) a75c78c40110 (diff)
children 1662f4df5de6
files
diffstat 26 files changed, 1392 insertions(+), 570 deletions(-) [+]
line wrap: on
line diff
--- a/css/core.css	Thu Jan 19 17:20:44 2017 +0000
+++ b/css/core.css	Mon Feb 13 12:20:48 2017 +0000
@@ -101,6 +101,12 @@
 table.popup-option-list tr td {
     padding: 5px;
 }
+div.survey-slider-text-holder {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    padding: 0px 15px;
+}
 button#popup-proceed {
     bottom: 10px;
     right: 10px;
@@ -245,3 +251,14 @@
     padding: 0 5px;
     height: 290px;
 }
+
+/*  Comment Boxes */
+
+div.comment-slider-text-holder {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+}
+div.comment-slider-text-holder span {
+    margin: 0px 5px;
+}
--- a/index.html	Thu Jan 19 17:20:44 2017 +0000
+++ b/index.html	Mon Feb 13 12:20:48 2017 +0000
@@ -17,6 +17,7 @@
     <link rel='stylesheet' type='text/css' href='css/core.css'>
     <!-- Use jQuery hosted from Google CDN -->
     <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>-->
+    <script type="text/javascript" src="https://cdn.rawgit.com/showdownjs/showdown/1.6.0/dist/showdown.min.js"></script>
     <script type="text/javascript" src="js/jquery-2.1.4.js"></script>
     <script type="text/javascript" src="js/loader.js"></script>
 </head>
@@ -43,9 +44,7 @@
     </div>
     <div id="popupHolder" class="popupHolder" style="visibility: hidden">
         <div id="popupContent">
-            <div id="popupTitleHolder">
-                <span id="popupTitle"></span>
-            </div>
+            <div id="popupTitleHolder"></div>
             <div id="popupResponse"></div>
         </div>
         <button id="popup-proceed" class="popupButton">Next</button>
--- a/interfaces/AB.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/AB.js	Mon Feb 13 12:20:48 2017 +0000
@@ -135,7 +135,7 @@
 
 function loadTest(audioHolderObject) {
     var feedbackHolder = document.getElementById('feedbackHolder');
-    var interfaceObj = audioHolderObject.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(audioHolderObject);
     if (interfaceObj.length > 1) {
         console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
     }
@@ -157,7 +157,7 @@
         document.getElementById("pageTitle").textContent = interfaceObj.title;
     }
 
-    var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
+    var interfaceOptions = interfaceObj.options;
     // Clear the interfaceElements
     {
         var node = document.getElementById('playback-holder');
@@ -316,7 +316,7 @@
             if (this.parent.specification.parent.playOne || specification.playOne) {
                 $('.comparator-button').text('Wait');
                 $('.comparator-button').attr("disabled", "true");
-                $(this.playback).removeAttr("disabled");
+                $(this.playback).css("disabled", "false");
             } else {
                 $('.comparator-button').text('Listen');
             }
@@ -351,6 +351,11 @@
     this.comparators = [];
     this.selected = null;
 
+    var labelType = audioHolderObject.label;
+    if (labelType == "default") {
+        labelType = "capital";
+    }
+
     // First generate the Audio Objects for the Audio Engine
     for (var index = 0; index < audioHolderObject.audioElements.length; index++) {
         var element = audioHolderObject.audioElements[index];
@@ -359,35 +364,7 @@
             var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder"));
             audioObject.bindInterface(orNode);
         } else {
-            var label;
-            if (audioObject.specification.label && audioObject.specification.label.length > 0) {
-                label = audioObject.specification.label;
-            } else {
-                switch (audioObject.specification.parent.label) {
-                    case "none":
-                        label = "";
-                        break;
-                    case "number":
-                        label = "" + index;
-                        break;
-                    case "letter":
-                        label = String.fromCharCode(97 + index);
-                        break;
-                    case "samediff":
-                        console.log("index = " + index);
-                        if (index == 0) {
-                            label = "same";
-                        } else if (index == 1) {
-                            label = "different";
-                        } else {
-                            label = "";
-                        }
-                        break;
-                    default:
-                        label = String.fromCharCode(65 + index);
-                        break;
-                }
-            }
+            var label = interfaceContext.getLabel(labelType, index, audioHolderObject.labelStart);
             var node = new this.comparatorBox(audioObject, index, label);
             audioObject.bindInterface(node);
             this.comparators.push(node);
@@ -418,10 +395,8 @@
 }
 
 function buttonSubmitClick() {
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
 
     for (var i = 0; i < checks.length; i++) {
         if (checks[i].type == 'check') {
--- a/interfaces/ABX.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/ABX.js	Mon Feb 13 12:20:48 2017 +0000
@@ -137,7 +137,7 @@
     // Called each time a new test page is to be build. The page specification node is the only item passed in
     document.getElementById('box-holders').innerHTML = "";
 
-    var interfaceObj = page.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(page);
     if (interfaceObj.length > 1) {
         console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
     }
@@ -157,7 +157,7 @@
 
     interfaceContext.comparator = new comparator(page);
 
-    var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
+    var interfaceOptions = interfaceObj.options;
     for (var option of interfaceOptions) {
         if (option.type == "show") {
             switch (option.name) {
@@ -364,19 +364,10 @@
         }
         var audioObject = audioEngineContext.newTrack(element);
         var label;
-        switch (audioObject.specification.parent.label) {
-            case "none":
-                label = "";
-                break;
-            case "number":
-                label = "" + index;
-                break;
-            case "letter":
-                label = String.fromCharCode(97 + index);
-                break;
-            default:
-                label = String.fromCharCode(65 + index);
-                break;
+        if (index == 0) {
+            label = "A";
+        } else {
+            label = "B";
         }
         var node = new this.interfaceObject(audioObject, label);
         audioObject.bindInterface(node);
@@ -439,10 +430,8 @@
 }
 
 function buttonSubmitClick() {
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
 
     for (var i = 0; i < checks.length; i++) {
         if (checks[i].type == 'check') {
--- a/interfaces/ape.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/ape.js	Mon Feb 13 12:20:48 2017 +0000
@@ -273,6 +273,10 @@
     feedbackHolder.innerHTML = "";
     sliderHolder.innerHTML = "";
 
+    // Set labelType if default to number
+    if (audioHolderObject.label == "default" || audioHolderObject.label == "") {
+        audioHolderObject.label = "number";
+    }
     // Set the page title
     if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
         document.getElementById("test-title").textContent = audioHolderObject.title
@@ -282,46 +286,47 @@
     // Delete outside reference
     document.getElementById("outside-reference-holder").innerHTML = "";
 
-    var interfaceObj = audioHolderObject.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(audioHolderObject);
     for (var k = 0; k < interfaceObj.length; k++) {
         // Create the div box to center align
         interfaceContext.interfaceSliders.push(new interfaceSliderHolder(interfaceObj[k]));
     }
-
-    var interfaceList = audioHolderObject.interfaces.concat(specification.interfaces);
-    for (var k = 0; k < interfaceList.length; k++) {
-        for (var i = 0; i < interfaceList[k].options.length; i++) {
-            if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'playhead') {
-                var playbackHolder = document.getElementById('playback-holder');
-                if (playbackHolder == null) {
-                    playbackHolder = document.createElement('div');
-                    playbackHolder.style.width = "100%";
-                    playbackHolder.align = 'center';
-                    playbackHolder.appendChild(interfaceContext.playhead.object);
-                    feedbackHolder.appendChild(playbackHolder);
+    interfaceObj.forEach(function (interface) {
+        for (var option of interface.options) {
+            if (option.type == "show") {
+                switch (option.name) {
+                    case "playhead":
+                        var playbackHolder = document.getElementById('playback-holder');
+                        if (playbackHolder == null) {
+                            playbackHolder = document.createElement('div');
+                            playbackHolder.style.width = "100%";
+                            playbackHolder.align = 'center';
+                            playbackHolder.appendChild(interfaceContext.playhead.object);
+                            feedbackHolder.insertBefore(playbackHolder, feedbackHolder.firstElementChild);
+                        }
+                        break;
+                    case "page-count":
+                        var pagecountHolder = document.getElementById('page-count');
+                        if (pagecountHolder == null) {
+                            pagecountHolder = document.createElement('div');
+                            pagecountHolder.id = 'page-count';
+                        }
+                        pagecountHolder.innerHTML = '<span>Page ' + (testState.stateIndex + 1) + ' of ' + testState.stateMap.length + '</span>';
+                        var inject = document.getElementById('interface-buttons');
+                        inject.appendChild(pagecountHolder);
+                        break;
+                    case "volume":
+                        if (document.getElementById('master-volume-holder') == null) {
+                            feedbackHolder.appendChild(interfaceContext.volume.object);
+                        }
+                        break;
+                    case "comments":
+                        interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true);
+                        break;
                 }
-            } else if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'page-count') {
-                var pagecountHolder = document.getElementById('page-count');
-                if (pagecountHolder == null) {
-                    pagecountHolder = document.createElement('div');
-                    pagecountHolder.id = 'page-count';
-                }
-                pagecountHolder.innerHTML = '<span>Page ' + (testState.stateIndex + 1) + ' of ' + testState.stateMap.length + '</span>';
-                var inject = document.getElementById('interface-buttons');
-                inject.appendChild(pagecountHolder);
-            } else if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'volume') {
-                if (document.getElementById('master-volume-holder') == null) {
-                    feedbackHolder.appendChild(interfaceContext.volume.object);
-                }
-            } else if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'comments') {
-                var commentHolder = document.createElement('div');
-                commentHolder.id = 'commentHolder';
-                document.getElementById('testContent').appendChild(commentHolder);
-                interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true);
-                break;
             }
         }
-    }
+    });
 
     var commentBoxPrefix = "Comment on fragment";
 
@@ -592,22 +597,8 @@
     // Create a new slider object;
     this.parent = audioObject;
     this.trackSliderObjects = [];
-    this.label = null;
+    this.label = interfaceContext.getLabel(audioObject.specification.parent.label, index, audioObject.specification.parent.labelStart);
     this.playing = false;
-    switch (audioObject.specification.parent.label) {
-        case "letter":
-            this.label = String.fromCharCode(97 + index);
-            break;
-        case "capital":
-            this.label = String.fromCharCode(65 + index);
-            break;
-        case "none":
-            this.label = "";
-            break;
-        default:
-            this.label = "" + (index + 1);
-            break;
-    }
     for (var i = 0; i < interfaceContext.interfaceSliders.length; i++) {
         var trackObj = interfaceContext.interfaceSliders[i].createSliderObject(audioObject, this.label);
         this.trackSliderObjects.push(trackObj);
@@ -746,10 +737,8 @@
 }
 
 function buttonSubmitClick() {
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
     if (interfaceContext.checkHiddenAnchor() == false) {
--- a/interfaces/discrete.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/discrete.js	Mon Feb 13 12:20:48 2017 +0000
@@ -120,7 +120,7 @@
 
     var feedbackHolder = document.getElementById('feedbackHolder');
     feedbackHolder.innerHTML = "";
-    var interfaceObj = page.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(page);
     if (interfaceObj.length > 1) {
         console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
     }
@@ -147,45 +147,7 @@
     }
     var loopPlayback = page.loop;
 
-    // Find all the audioElements from the audioHolder
-    var index = 0;
-    var interfaceScales = testState.currentStateMap.interfaces[0].scales;
-    $(page.audioElements).each(function (index, element) {
-        // Find URL of track
-        // In this jQuery loop, variable 'this' holds the current audioElement.
-
-        var audioObject = audioEngineContext.newTrack(element);
-        if (element.type == 'outside-reference') {
-            // Construct outside reference;
-            var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder"));
-            audioObject.bindInterface(orNode);
-        } else {
-            // Create a slider per track
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
-            var sliderObj = new discreteObject(audioObject, label, interfaceScales);
-            sliderBox.appendChild(sliderObj.holder);
-            audioObject.bindInterface(sliderObj);
-            interfaceContext.commentBoxes.createCommentBox(audioObject);
-            index += 1;
-        }
-
-    });
-
-    var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
-    for (var option of interfaceOptions) {
+    for (var option of interfaceObj.options) {
         if (option.type == "show") {
             switch (option.name) {
                 case "playhead":
@@ -220,6 +182,34 @@
         }
     }
 
+    // Find all the audioElements from the audioHolder
+    var index = 0;
+    var interfaceScales = page.interfaces[0].scales;
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(page.audioElements).each(function (pageIndex, element) {
+        // Find URL of track
+        // In this jQuery loop, variable 'this' holds the current audioElement.
+
+        var audioObject = audioEngineContext.newTrack(element);
+        if (element.type == 'outside-reference') {
+            // Construct outside reference;
+            var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder"));
+            audioObject.bindInterface(orNode);
+        } else {
+            // Create a slider per track
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
+            var sliderObj = new discreteObject(audioObject, label, interfaceScales);
+            sliderBox.appendChild(sliderObj.holder);
+            audioObject.bindInterface(sliderObj);
+            interfaceContext.commentBoxes.createCommentBox(audioObject);
+            index += 1;
+        }
+
+    });
+
     $(page.commentQuestions).each(function (index, element) {
         var node = interfaceContext.createCommentQuestion(element);
         feedbackHolder.appendChild(node.holder);
@@ -452,10 +442,8 @@
 
 function buttonSubmitClick() // TODO: Only when all songs have been played!
 {
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
     if (interfaceContext.checkHiddenAnchor() == false) {
--- a/interfaces/horizontal-sliders.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/horizontal-sliders.js	Mon Feb 13 12:20:48 2017 +0000
@@ -120,7 +120,7 @@
     var feedbackHolder = document.getElementById('feedbackHolder');
     feedbackHolder.innerHTML = "";
 
-    var interfaceObj = page.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(page);
     if (interfaceObj.length > 1) {
         console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
     }
@@ -154,7 +154,11 @@
 
     // Find all the audioElements from the audioHolder
     var index = 0;
-    $(page.audioElements).each(function (index, element) {
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(page.audioElements).each(function (pageIndex, element) {
         // Find URL of track
         // In this jQuery loop, variable 'this' holds the current audioElement.
 
@@ -165,20 +169,7 @@
             audioObject.bindInterface(orNode);
         } else {
             // Create a slider per track
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var sliderObj = new sliderObject(audioObject, label);
 
             if (typeof page.initialPosition === "number") {
@@ -195,8 +186,7 @@
         }
 
     });
-    var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
-    for (var option of interfaceOptions) {
+    for (var option of interfaceObj.options) {
         if (option.type == "show") {
             switch (option.name) {
                 case "playhead":
@@ -404,10 +394,8 @@
 
 function buttonSubmitClick() // TODO: Only when all songs have been played!
 {
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
     if (interfaceContext.checkHiddenAnchor() == false) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interfaces/interfaces.json	Mon Feb 13 12:20:48 2017 +0000
@@ -0,0 +1,35 @@
+{
+    "interfaces": [
+        {
+            "name": "APE",
+            "scripts": ["interfaces/ape.js"],
+            "css": ["interfaces/ape.css"]
+        },
+        {
+            "name": "MUSHRA",
+            "scripts": ["interfaces/mushra.js"],
+            "css": ["interfaces/mushra.css"]
+        },
+        {
+            "name": "horizontal",
+            "scripts": ["interfaces/horizontal-sliders.js"],
+            "css": ["interfaces/horizontal-sliders.css"]
+        }, {
+            "name": "discrete",
+            "scripts": ["interfaces/discrete.js"],
+            "css": ["interfaces/discrete.css"]
+        }, {
+            "name": "AB",
+            "scripts": ["interfaces/AB.js"],
+            "css": ["interfaces/AB.css"]
+        }, {
+            "name": "ABX",
+            "scripts": ["interfaces/ABX.js"],
+            "css": ["interfaces/ABX.css"]
+        }, {
+            "name": "timeline",
+            "scripts": ["interfaces/timeline.js"],
+            "css": ["interfaces/timeline.css"]
+        }
+    ]
+}
--- a/interfaces/mushra.css	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/mushra.css	Mon Feb 13 12:20:48 2017 +0000
@@ -7,43 +7,52 @@
     /* Set the background colour (note US English spelling) to grey*/
     background-color: #ddd
 }
+
 div.pageTitle {
     width: auto;
     height: 20px;
     margin: 10px 0px;
 }
+
 div.pageTitle span {
     font-size: 1.5em;
 }
+
 button {
     /* Specify any button structure or style */
     min-width: 20px;
     background-color: #ddd
 }
+
 div#slider-holder {
     height: inherit;
     position: absolute;
     left: 0px;
     z-index: 3;
 }
+
 div#scale-holder {
     height: inherit;
     position: absolute;
     left: 0px;
     z-index: 2;
 }
+
 div#scale-text-holder {
     position: relative;
     width: 100px;
     float: left;
 }
+
 div.scale-text {
     position: absolute;
 }
+
 canvas#scale-canvas {
     position: relative;
     float: left;
 }
+
 div.track-slider {
     float: left;
     width: 94px;
@@ -53,22 +62,27 @@
     padding: 2px;
     margin-left: 50px;
 }
+
 div#outside-reference-holder {
     display: flex;
     align-content: center;
     justify-content: center;
     margin-bottom: 5px;
 }
+
 button.outside-reference {
     position: inherit;
     margin: 0px 5px;
 }
+
 div.track-slider-playing {
     background-color: #FFDDDD;
 }
+
 input.track-slider-range {
     margin: 2px 0px;
 }
+
 input[type=range][orient=vertical] {
     writing-mode: bt-lr;
     /* IE */
@@ -78,6 +92,7 @@
     padding: 0 5px;
     color: rgb(255, 144, 144);
 }
+
 input[type=range]::-webkit-slider-runnable-track {
     width: 8px;
     cursor: pointer;
@@ -85,6 +100,7 @@
     border-radius: 4px;
     border: 1px solid #000;
 }
+
 input[type=range]::-moz-range-track {
     width: 8px;
     cursor: pointer;
@@ -92,41 +108,79 @@
     border-radius: 4px;
     border: 1px solid #000;
 }
+
 input[type=range]::-ms-track {
     cursor: pointer;
     background: #fff;
     border-radius: 4px;
     border: 1px solid #000;
 }
+
 input.track-slider-not-moved[type=range]::-webkit-slider-runnable-track {
     background: #aaa;
 }
+
 input.track-slider-not-moved[type=range]::-moz-range-track {
     background: #aaa;
 }
+
 input[type=range]::-moz-range-thumb {
     margin-left: -7px;
     cursor: pointer;
     margin-top: -1px;
     box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
 }
+
 input[type=range]::-webkit-slider-thumb {
     cursor: pointer;
     margin-top: -1px;
     margin-left: -4px;
 }
+
 input[type=range]::-ms-thumb {
     cursor: pointer;
     margin-top: -1px;
     margin-left: -4px;
 }
+
 input[type=range]::-ms-tooltip {
     visibility: hidden;
 }
+
+input.track-slider-range-disabled {}
+
+input.track-slider-range-disabled[type=range]::-webkit-slider-runnable-track {
+    cursor: not-allowed;
+}
+
+input.track-slider-range-disabled[type=range]::-moz-range-track {
+    cursor: not-allowed;
+}
+
+input.track-slider-range-disabled[type=range]::-ms-track {
+    cursor: not-allowed;
+}
+
+input.track-slider-range-disabled[type=range]::-moz-range-thumb {
+    cursor: not-allowed;
+    background-color: #888;
+}
+
+input.track-slider-range-disabled[type=range]::-webkit-slider-thumb {
+    cursor: not-allowed;
+    background-color: #888;
+}
+
+input.track-slider-range-disabled[type=range]::-ms-thumb {
+    cursor: not-allowed;
+    background-color: #888;
+}
+
 div#page-count {
     float: left;
     margin: 0px 5px;
 }
+
 div#master-volume-holder {
     position: absolute;
     top: 10px;
--- a/interfaces/mushra.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/mushra.js	Mon Feb 13 12:20:48 2017 +0000
@@ -121,7 +121,7 @@
 
     var feedbackHolder = document.getElementById('feedbackHolder');
     feedbackHolder.innerHTML = "";
-    var interfaceObj = audioHolderObject.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(audioHolderObject);
     if (interfaceObj.length > 1) {
         console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
     }
@@ -155,7 +155,12 @@
 
     // Find all the audioElements from the audioHolder
     var index = 0;
-    $(audioHolderObject.audioElements).each(function (index, element) {
+    var interfaceScales = testState.currentStateMap.interfaces[0].scales;
+    var labelType = audioHolderObject.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(audioHolderObject.audioElements).each(function (pageIndex, element) {
         // Find URL of track
         // In this jQuery loop, variable 'this' holds the current audioElement.
 
@@ -166,20 +171,7 @@
             audioObject.bindInterface(orNode);
         } else {
             // Create a slider per track
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, audioHolderObject.labelStart);
             var sliderObj = new sliderObject(audioObject, label);
 
             if (typeof audioHolderObject.initialPosition === "number") {
@@ -197,8 +189,15 @@
 
     });
 
+    if (testState.currentStateMap.restrictMovement) {
+        $(".track-slider-range").addClass("track-slider-range-disabled");
+        $(".track-slider-range").each(function (i, e) {
+            e.disabled = true
+        });
+    }
 
-    var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
+
+    var interfaceOptions = interfaceObj.options;
     for (var option of interfaceOptions) {
         if (option.type == "show") {
             switch (option.name) {
@@ -209,7 +208,7 @@
                         playbackHolder.style.width = "100%";
                         playbackHolder.align = 'center';
                         playbackHolder.appendChild(interfaceContext.playhead.object);
-                        feedbackHolder.appendChild(playbackHolder);
+                        feedbackHolder.insertBefore(playbackHolder, feedbackHolder.firstElementChild);
                     }
                     break;
                 case "page-count":
@@ -245,6 +244,7 @@
 
 function sliderObject(audioObject, label) {
     // Constructs the slider object. We use the HTML5 slider object
+    var page = testState.currentStateMap;
     this.parent = audioObject;
     this.holder = document.createElement('div');
     this.title = document.createElement('span');
@@ -310,6 +310,7 @@
         return node;
     };
     this.startPlayback = function () {
+        var self = this;
         // Called when playback has begun
         this.play.setAttribute("playstate", "playing");
         $(".track-slider").removeClass('track-slider-playing');
@@ -319,12 +320,32 @@
             $(outsideReference).removeClass('track-slider-playing');
         }
         this.play.textContent = "Stop";
+        if (page.restrictMovement) {
+            if (page.loop) {
+                $(this.slider).removeClass("track-slider-range-disabled");
+                this.slider.removeAttribute("disabled");
+            } else {
+                $(".track-slider-range").addClass("track-slider-range-disabled");
+                $(this.slider).removeClass("track-slider-range-disabled");
+                $(".track-slider-range").each(function (i, m) {
+                    if (m == self.slider) {
+                        m.removeAttribute("disabled");
+                    } else {
+                        m.setAttribute("disabled", "true");
+                    }
+                });
+            }
+        }
     };
     this.stopPlayback = function () {
         // Called when playback has stopped. This gets called even if playback never started!
         this.play.setAttribute("playstate", "ready");
         $(this.holder).removeClass('track-slider-playing');
         this.play.textContent = "Play";
+        if (page.restrictMovement && page.loop) {
+            $(this.slider).addClass("track-slider-range-disabled");
+            this.slider.setAttribute("disabled", "true");
+        }
     };
     this.getValue = function () {
         return this.slider.value;
@@ -430,10 +451,8 @@
 
 function buttonSubmitClick() // TODO: Only when all songs have been played!
 {
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
     if (interfaceContext.checkHiddenAnchor() == false) {
--- a/interfaces/timeline.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/interfaces/timeline.js	Mon Feb 13 12:20:48 2017 +0000
@@ -83,7 +83,7 @@
     // Called each time a new test page is to be build. The page specification node is the only item passed in
     var content = document.getElementById("timeline-test-content");
     content.innerHTML = "";
-    var interfaceObj = page.interfaces;
+    var interfaceObj = interfaceContext.getCombinedInterfaces(page);
     if (interfaceObj.length > 1) {
         console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
     }
@@ -106,27 +106,19 @@
     if (interfaceObj.commentBoxPrefix != undefined) {
         commentBoxPrefix = interfaceObj.commentBoxPrefix;
     }
-
-    $(page.audioElements).each(function (index, element) {
+    var index = 0;
+    var interfaceScales = testState.currentStateMap.interfaces[0].scales;
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(page.audioElements).each(function (pageIndex, element) {
         var audioObject = audioEngineContext.newTrack(element);
         if (page.audioElements.type == 'outside-reference') {
             var refNode = interfaceContext.outsideReferenceDOM(audioObject, index, outsideReferenceHolder);
             audioObject.bindInterface(orNode);
         } else {
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var node = new interfaceObject(audioObject, label);
 
             content.appendChild(node.DOM);
@@ -482,10 +474,8 @@
         interfaceContext.lightbox.post("Warning", 'You have not started the test! Please click play on a sample to begin the test!');
         return;
     }
-    var checks = [];
-    checks = checks.concat(testState.currentStateMap.interfaces[0].options);
-    checks = checks.concat(specification.interfaces.options);
-    var canContinue = true;
+    var checks = testState.currentStateMap.interfaces[0],
+        canContinue = true;
     for (var i = 0; i < checks.length; i++) {
         var checkState = true;
         if (checks[i].type == 'check') {
@@ -506,7 +496,7 @@
                     break;
             }
             if (checkState == false) {
-                canContinue == false;
+                canContinue = false;
             }
         }
         if (!canContinue) {
--- a/js/core.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/js/core.js	Mon Feb 13 12:20:48 2017 +0000
@@ -331,98 +331,40 @@
         }
     }
 
-    // Detect the interface to use and load the relevant javascripts.
-    var interfaceJS = document.createElement('script');
-    interfaceJS.setAttribute("type", "text/javascript");
-    switch (specification.interface) {
-        case "APE":
-            interfaceJS.setAttribute("src", "interfaces/ape.js");
-
-            // APE comes with a css file
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/ape.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
-
-        case "MUSHRA":
-            interfaceJS.setAttribute("src", "interfaces/mushra.js");
-
-            // MUSHRA comes with a css file
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/mushra.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
-
-        case "AB":
-            interfaceJS.setAttribute("src", "interfaces/AB.js");
-
-            // AB comes with a css file
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/AB.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
-
-        case "ABX":
-            interfaceJS.setAttribute("src", "interfaces/ABX.js");
-
-            // AB comes with a css file
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/ABX.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
-
-        case "Bipolar":
-        case "ACR":
-        case "DCR":
-        case "CCR":
-        case "ABC":
-            // Above enumerate to horizontal sliders
-            interfaceJS.setAttribute("src", "interfaces/horizontal-sliders.js");
-
-            // horizontal-sliders comes with a css file
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/horizontal-sliders.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
-        case "discrete":
-        case "likert":
-            // Above enumerate to horizontal discrete radios
-            interfaceJS.setAttribute("src", "interfaces/discrete.js");
-
-            // horizontal-sliders comes with a css file
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/discrete.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
-        case "timeline":
-            interfaceJS.setAttribute("src", "interfaces/timeline.js");
-            var css = document.createElement('link');
-            css.rel = 'stylesheet';
-            css.type = 'text/css';
-            css.href = 'interfaces/timeline.css';
-
-            document.getElementsByTagName("head")[0].appendChild(css);
-            break;
+    var getInterfaces = new XMLHttpRequest();
+    getInterfaces.open("GET", "interfaces/interfaces.json");
+    getInterfaces.onerror = function (e) {
+        throw (e);
     }
-    document.getElementsByTagName("head")[0].appendChild(interfaceJS);
+    getInterfaces.onload = function () {
+        if (getInterfaces.status !== 200) {
+            throw (new Error(getInterfaces.status));
+        }
+        // Get the current interface
+        var name = specification.interface,
+            head = document.getElementsByTagName("head")[0],
+            data = JSON.parse(getInterfaces.responseText),
+            interfaceObject = data.interfaces.find(function (e) {
+                return e.name == name;
+            });
+        if (!interfaceObject) {
+            throw ("Cannot load desired interface");
+        }
+        interfaceObject.scripts.forEach(function (v) {
+            var script = document.createElement("script");
+            script.setAttribute("type", "text/javascript");
+            script.setAttribute("src", v);
+            head.appendChild(script);
+        });
+        interfaceObject.css.forEach(function (v) {
+            var css = document.createElement("link");
+            css.setAttribute("rel", "stylesheet");
+            css.setAttribute("type", "text/css");
+            css.setAttribute("href", v);
+            head.appendChild(css);
+        });
+    }
+    getInterfaces.send();
 
     if (gReturnURL != undefined) {
         console.log("returnURL Overide from " + specification.returnURL + " to " + gReturnURL);
@@ -653,7 +595,7 @@
 
         this.popupContent = document.getElementById('popupContent');
 
-        this.popupTitle = document.getElementById('popupTitle');
+        this.popupTitle = document.getElementById('popupTitleHolder');
 
         this.popupResponse = document.getElementById('popupResponse');
 
@@ -692,9 +634,12 @@
 
     this.postNode = function () {
         // This will take the node from the popupOptions and display it
-        var node = this.popupOptions[this.currentIndex];
+        var node = this.popupOptions[this.currentIndex],
+            converter = new showdown.Converter(),
+            p = new DOMParser();
         this.popupResponse.innerHTML = "";
-        this.popupTitle.textContent = node.specification.statement;
+        this.popupTitle.innerHTML = "";
+        this.popupTitle.appendChild(p.parseFromString(converter.makeHtml(node.specification.statement), "text/html").getElementsByTagName("body")[0].firstElementChild);
         if (node.specification.type == 'question') {
             var textArea = document.createElement('textarea');
             switch (node.specification.boxsize) {
@@ -799,13 +744,13 @@
         } else if (node.specification.type == 'number') {
             var input = document.createElement('input');
             input.type = 'textarea';
-            if (node.min != null) {
+            if (node.specification.min != null) {
                 input.min = node.specification.min;
             }
-            if (node.max != null) {
+            if (node.specification.max != null) {
                 input.max = node.specification.max;
             }
-            if (node.step != null) {
+            if (node.specification.step != null) {
                 input.step = node.specification.step;
             }
             if (node.response != undefined) {
@@ -823,6 +768,30 @@
             iframe.className = "youtube";
             iframe.src = node.specification.url;
             this.popupResponse.appendChild(iframe);
+        } else if (node.specification.type == "slider") {
+            var hold = document.createElement('div');
+            var input = document.createElement('input');
+            input.type = 'range';
+            input.style.width = "90%";
+            if (node.specification.min != null) {
+                input.min = node.specification.min;
+            }
+            if (node.specification.max != null) {
+                input.max = node.specification.max;
+            }
+            if (node.response != undefined) {
+                input.value = node.response;
+            }
+            hold.className = "survey-slider-text-holder";
+            var minText = document.createElement('span');
+            var maxText = document.createElement('span');
+            minText.textContent = node.specification.leftText;
+            maxText.textContent = node.specification.rightText;
+            hold.appendChild(minText);
+            hold.appendChild(maxText);
+            this.popupResponse.appendChild(input);
+            this.popupResponse.appendChild(hold);
+            this.popupResponse.style.textAlign = "center";
         }
         if (this.currentIndex + 1 == this.popupOptions.length) {
             if (this.node.location == "pre") {
@@ -922,6 +891,33 @@
             console.log("Checkbox: " + node.specification.statement);
             var inputs = this.popupResponse.getElementsByTagName('input');
             node.response = [];
+            var numChecked = 0;
+            for (var i = 0; i < node.specification.options.length; i++) {
+                if (inputs[i].checked) {
+                    numChecked++;
+                }
+            }
+            if (node.specification.min != undefined) {
+                if (node.specification.max == undefined) {
+                    if (numChecked < node.specification.min) {
+                        var msg = "You must select at least " + node.specification.min + " option";
+                        if (node.specification.min > 1) {
+                            msg += "s";
+                        }
+                        interfaceContext.lightbox.post("Error", msg);
+                        return;
+                    }
+                } else {
+                    if (numChecked < node.specification.min || numChecked > node.specification.max) {
+                        if (node.specification.min == node.specification.max) {
+                            interfaceContext.lightbox.post("Error", "You must only select " + node.specification.min);
+                        } else {
+                            interfaceContext.lightbox.post("Error", "You must select between " + node.specification.min + " and " + node.specification.max);
+                        }
+                        return;
+                    }
+                }
+            }
             for (var i = 0; i < node.specification.options.length; i++) {
                 node.response.push({
                     name: node.specification.options[i].name,
@@ -1080,13 +1076,56 @@
                     break;
                 }
             }
+        } else if (node.specification.type == 'slider') {
+            var input = this.popupContent.getElementsByTagName('input')[0];
+            node.response = input.value;
+            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) {
             this.postNode();
         } else {
             // Reached the end of the popupOptions
-            this.popupTitle.textContent = "";
+            this.popupTitle.innerHTML = "";
             this.popupResponse.innerHTML = "";
             this.hidePopup();
             for (var node of this.popupOptions) {
@@ -1394,10 +1433,8 @@
 
     this.buffers = [];
     this.bufferObj = function () {
-        this.url = null;
+        var urls = [];
         this.buffer = null;
-        this.xmlRequest = new XMLHttpRequest();
-        this.xmlRequest.parent = this;
         this.users = [];
         this.progress = 0;
         this.status = 0;
@@ -1412,30 +1449,77 @@
                 }
             }
         };
-        this.getMedia = function (url) {
-            this.url = url;
-            this.xmlRequest.open('GET', this.url, true);
-            this.xmlRequest.responseType = 'arraybuffer';
+        this.setUrls = function (obj) {
+            // Obj must be an array of pairs:
+            // [{sampleRate, url}]
+            var localFs = audioContext.sampleRate,
+                list = [],
+                i;
+            for (i = 0; i < obj.length; i++) {
+                if (obj[i].sampleRate == localFs) {
+                    list.push(obj.splice(i, 1)[0]);
+                }
+            }
+            list = list.concat(obj);
+            urls = list;
+        };
+        this.hasUrl = function (checkUrl) {
+            var l = urls.length,
+                i;
+            for (i = 0; i < l; i++) {
+                if (urls[i].url == checkUrl) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        this.getMedia = function () {
+            var self = this;
+            var currentUrlIndex = 0;
 
-            var bufferObj = this;
+            function get(fqurl) {
+                return new Promise(function (resolve, reject) {
+                    var req = new XMLHttpRequest();
+                    req.open('GET', fqurl, true);
+                    req.responseType = 'arraybuffer';
+                    req.onload = function () {
+                        if (req.status == 200) {
+                            resolve(req.response);
+                        }
+                    };
+                    req.onerror = function () {
+                        reject(new Error(req.statusText));
+                    };
+
+                    req.addEventListener("progress", progressCallback.bind(self));
+                    req.send();
+                });
+            }
+
+            function getNextURL() {
+                currentUrlIndex++;
+                var self = this;
+                if (currentUrlIndex >= urls.length) {
+                    processError();
+                } else {
+                    return get(urls[currentUrlIndex].url).then(processAudio.bind(self)).catch(getNextURL.bind(self));
+                }
+            }
 
             // Create callback to decode the data asynchronously
-            this.xmlRequest.onloadend = function () {
-                // Use inbuilt WAVE decoder first
-                if (this.status == -1) {
-                    return;
-                }
-                var waveObj = new WAVE();
-                audioContext.decodeAudioData(bufferObj.xmlRequest.response, function (decodedData) {
-                    bufferObj.buffer = decodedData;
-                    bufferObj.status = 2;
-                    calculateLoudness(bufferObj, "I");
+            function processAudio(response) {
+                var self = this;
+                return audioContext.decodeAudioData(response, function (decodedData) {
+                    self.buffer = decodedData;
+                    self.status = 2;
+                    calculateLoudness(self, "I");
+                    return true;
                 }, function (e) {
                     var waveObj = new WAVE();
-                    if (waveObj.open(bufferObj.xmlRequest.response) == 0) {
-                        bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels, waveObj.num_samples, waveObj.sample_rate);
+                    if (waveObj.open(response) == 0) {
+                        self.buffer = audioContext.createBuffer(waveObj.num_channels, waveObj.num_samples, waveObj.sample_rate);
                         for (var c = 0; c < waveObj.num_channels; c++) {
-                            var buffer_ptr = bufferObj.buffer.getChannelData(c);
+                            var buffer_ptr = self.buffer.getChannelData(c);
                             for (var n = 0; n < waveObj.num_samples; n++) {
                                 buffer_ptr[n] = waveObj.decoded_data[c][n];
                             }
@@ -1443,41 +1527,44 @@
 
                         delete waveObj;
                     }
-                    if (bufferObj.buffer != undefined) {
-                        bufferObj.status = 2;
-                        calculateLoudness(bufferObj, "I");
+                    if (self.buffer != undefined) {
+                        self.status = 2;
+                        calculateLoudness(self, "I");
+                        return true;
                     }
+                    return false;
                 });
-            };
+            }
 
             // Create callback for any error in loading
-            this.xmlRequest.onerror = function () {
-                this.parent.status = -1;
-                for (var i = 0; i < this.parent.users.length; i++) {
-                    this.parent.users[i].state = -1;
-                    if (this.parent.users[i].interfaceDOM != null) {
-                        this.parent.users[i].bufferLoaded(this);
+            function processError() {
+                this.status = -1;
+                for (var i = 0; i < this.users.length; i++) {
+                    this.users[i].state = -1;
+                    if (this.users[i].interfaceDOM != null) {
+                        this.users[i].bufferLoaded(this);
                     }
                 }
-                interfaceContext.lightbox.post("Error", "Could not load resource " + this.parent.url);
+                interfaceContext.lightbox.post("Error", "Could not load resource " + urls[currentUrlIndex].url);
             }
 
-            this.progress = 0;
-            this.progressCallback = function (event) {
+            function progressCallback(event) {
                 if (event.lengthComputable) {
-                    this.parent.progress = event.loaded / event.total;
-                    for (var i = 0; i < this.parent.users.length; i++) {
-                        if (this.parent.users[i].interfaceDOM != null) {
-                            if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function") {
-                                this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress * 100);
+                    this.progress = event.loaded / event.total;
+                    for (var i = 0; i < this.users.length; i++) {
+                        if (this.users[i].interfaceDOM != null) {
+                            if (typeof this.users[i].interfaceDOM.updateLoading === "function") {
+                                this.users[i].interfaceDOM.updateLoading(this.progress * 100);
                             }
                         }
                     }
                 }
             };
-            this.xmlRequest.addEventListener("progress", this.progressCallback);
+
+            this.progress = 0;
             this.status = 1;
-            this.xmlRequest.send();
+            currentUrlIndex = 0;
+            get(urls[0].url).then(processAudio.bind(self)).catch(getNextURL.bind(self));
         };
 
         this.registerAudioObject = function (audioObject) {
@@ -1554,14 +1641,25 @@
             var URL = page.hostURL + element.url;
             var buffer = null;
             for (var buffObj of this.buffers) {
-                if (URL == buffObj.url) {
+                if (buffObj.hasUrl(URL)) {
                     buffer = buffObj;
                     break;
                 }
             }
             if (buffer == null) {
                 buffer = new this.bufferObj();
-                buffer.getMedia(URL);
+                var urls = [{
+                    url: URL,
+                    sampleRate: element.sampleRate
+                }];
+                element.alternatives.forEach(function (e) {
+                    urls.push({
+                        url: e.url,
+                        sampleRate: e.sampleRate
+                    });
+                });
+                buffer.setUrls(urls);
+                buffer.getMedia();
                 this.buffers.push(buffer);
             }
         }
@@ -1631,7 +1729,7 @@
         var URL = testState.currentStateMap.hostURL + element.url;
         var buffer = null;
         for (var i = 0; i < this.buffers.length; i++) {
-            if (URL == this.buffers[i].url) {
+            if (this.buffers[i].hasUrl(URL)) {
                 buffer = this.buffers[i];
                 break;
             }
@@ -1694,21 +1792,20 @@
     };
 
     this.setSynchronousLoop = function () {
-        // Pads the signals so they are all exactly the same length
-        // Get the length of the longest signal.
-        var length = 0;
+        // Pads the signals so they are all exactly the same duration
+        // Get the duration of the longest signal.
+        var duration = 0;
         var maxId;
         for (var i = 0; i < this.audioObjects.length; i++) {
-            if (length < this.audioObjects[i].buffer.buffer.length) {
-                length = this.audioObjects[i].buffer.buffer.length;
+            if (duration < this.audioObjects[i].buffer.buffer.duration) {
+                duration = this.audioObjects[i].buffer.buffer.duration;
                 maxId = i;
             }
         }
         // Extract the audio and zero-pad
         for (var ao of this.audioObjects) {
-            var lengthDiff = length - ao.buffer.buffer.length;
-            if (lengthDiff > 0) {
-                ao.buffer.buffer = ao.buffer.copyBuffer(0, samplesToSeconds(lengthDiff, ao.buffer.buffer.sampleRate));
+            if (ao.buffer.buffer.duration !== duration) {
+                ao.buffer.buffer = ao.buffer.copyBuffer(0, duration - ao.buffer.buffer.duration);
             }
         }
     };
@@ -2603,6 +2700,63 @@
         this.resize();
     };
 
+    this.sliderBox = function (commentQuestion) {
+        this.specification = commentQuestion;
+        this.holder = document.createElement("div");
+        this.holder.className = 'comment-div';
+        this.string = document.createElement("span");
+        this.string.innerHTML = commentQuestion.statement;
+        this.slider = document.createElement("input");
+        this.slider.type = "range";
+        this.slider.min = commentQuestion.min;
+        this.slider.max = commentQuestion.max;
+        this.slider.step = commentQuestion.step;
+        this.slider.value = commentQuestion.value;
+        var br = document.createElement('br');
+
+        var textHolder = document.createElement("div");
+        textHolder.className = "comment-slider-text-holder";
+
+        this.leftText = document.createElement("span");
+        this.leftText.textContent = commentQuestion.leftText;
+        this.rightText = document.createElement("span");
+        this.rightText.textContent = commentQuestion.rightText;
+        textHolder.appendChild(this.leftText);
+        textHolder.appendChild(this.rightText);
+
+        this.holder.appendChild(this.string);
+        this.holder.appendChild(br);
+        this.holder.appendChild(this.slider);
+        this.holder.appendChild(textHolder);
+
+        this.exportXMLDOM = function (storePoint) {
+            var root = storePoint.parent.document.createElement('comment');
+            root.id = this.specification.id;
+            root.setAttribute('type', this.specification.type);
+            console.log("Question: " + this.string.textContent);
+            console.log("Response: " + this.slider.value);
+            var question = storePoint.parent.document.createElement('question');
+            question.textContent = this.string.textContent;
+            var response = storePoint.parent.document.createElement('response');
+            response.textContent = this.slider.value;
+            root.appendChild(question);
+            root.appendChild(response);
+            storePoint.XMLDOM.appendChild(root);
+            return root;
+        };
+        this.resize = function () {
+            var boxwidth = (window.innerWidth - 100) / 2;
+            if (boxwidth >= 600) {
+                boxwidth = 600;
+            } else if (boxwidth < 400) {
+                boxwidth = 400;
+            }
+            this.holder.style.width = boxwidth + "px";
+            this.slider.style.width = boxwidth - 24 + "px";
+        };
+        this.resize();
+    };
+
     this.createCommentQuestion = function (element) {
         var node;
         if (element.type == 'question') {
@@ -2611,6 +2765,8 @@
             node = new this.radioBox(element);
         } else if (element.type == 'checkbox') {
             node = new this.checkboxBox(element);
+        } else if (element.type == 'slider') {
+            node = new this.sliderBox(element);
         }
         this.commentQuestions.push(node);
         return node;
@@ -2886,8 +3042,8 @@
                 obj.input.value = obj.gain.gain.value;
                 obj.input.setAttribute('orient', 'vertical');
                 obj.input.type = "range";
-                obj.input.min = -6;
-                obj.input.max = 6;
+                obj.input.min = -12;
+                obj.input.max = 0;
                 obj.input.step = 0.25;
                 if (f0 != 1000) {
                     obj.input.value = (Math.random() * 12) - 6;
@@ -3102,6 +3258,104 @@
         node.textContent = errorMessage;
         testState.currentStore.XMLDOM.appendChild(node);
     };
+
+    this.getLabel = function (labelType, index, labelStart) {
+        /*
+            Get the correct label based on type, index and offset
+        */
+
+        function calculateLabel(labelType, index, offset) {
+            if (labelType == "none") {
+                return "";
+            }
+            switch (labelType) {
+                case "letter":
+                    return String.fromCharCode((index + offset) % 26 + 97);
+                case "capital":
+                    return String.fromCharCode((index + offset) % 26 + 65);
+                case "samediff":
+                    if (index == 0) {
+                        return "Same";
+                    } else if (index == 1) {
+                        return "Difference";
+                    } else {
+                        return "";
+                    }
+                case "number":
+                    return String(index + offset);
+                default:
+                    return "";
+            }
+        }
+
+        if (typeof labelStart !== "string" || labelStart.length == 0) {
+            labelStart = String.fromCharCode(0);
+        }
+
+        switch (labelType) {
+            case "letter":
+                labelStart = labelStart.charCodeAt(0);
+                if (labelStart < 97 || labelStart > 122) {
+                    labelStart = 97;
+                }
+                labelStart -= 97;
+                break;
+            case "capital":
+                labelStart = labelStart.charCodeAt(0);
+                if (labelStart < 65 || labelStart > 90) {
+                    labelStart = 65;
+                }
+                labelStart -= 65;
+                break;
+            case "number":
+                labelStart = Number(labelStart);
+                if (!isFinite(labelStart)) {
+                    labelStart = 1;
+                }
+                break;
+            case "none":
+            default:
+                labelStart = 0;
+        }
+        if (typeof index == "number") {
+            return calculateLabel(labelType, index, labelStart);
+        } else if (index.length && index.length > 0) {
+            var a = [],
+                l = index.length,
+                i;
+            for (i = 0; i < l; i++) {
+                a[i] = calculateLabel(labelType, index[i], labelStart);
+            }
+            return a;
+        } else {
+            throw ("Invalid arguments");
+        }
+    }
+
+    this.getCombinedInterfaces = function (page) {
+        // Combine the interfaces with the global interface nodes
+        var global = specification.interfaces,
+            local = page.interfaces;
+        local.forEach(function (locInt) {
+            // Iterate through the options nodes
+            var addList = [];
+            global.options.forEach(function (gopt) {
+                var lopt = locInt.options.find(function (lopt) {
+                    return (lopt.name == gopt.name) && (lopt.type == gopt.type);
+                });
+                if (!lopt) {
+                    // Global option doesn't exist locally
+                    addList.push(gopt);
+                }
+            });
+            locInt.options = locInt.options.concat(addList);
+            if (!locInt.scales && global.scales) {
+                // Use the global default scales
+                locInt.scales = global.scales;
+            }
+        });
+        return local;
+    }
 }
 
 function Storage() {
@@ -3257,14 +3511,17 @@
             switch (node.specification.type) {
                 case "number":
                 case "question":
+                case "slider":
                     var child = this.parent.document.createElement('response');
                     child.textContent = node.response;
                     surveyresult.appendChild(child);
                     break;
                 case "radio":
                     var child = this.parent.document.createElement('response');
-                    child.setAttribute('name', node.response.name);
-                    child.textContent = node.response.text;
+                    if (node.response !== null) {
+                        child.setAttribute('name', node.response.name);
+                        child.textContent = node.response.text;
+                    }
                     surveyresult.appendChild(child);
                     break;
                 case "checkbox":
--- a/js/specification.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/js/specification.js	Mon Feb 13 12:20:48 2017 +0000
@@ -195,7 +195,7 @@
 
         this.OptionNode = function (specification) {
             this.type = undefined;
-            this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
+            this.schema = undefined;
             this.id = undefined;
             this.name = undefined;
             this.mandatory = undefined;
@@ -208,6 +208,7 @@
             this.conditions = [];
 
             this.decode = function (parent, child) {
+                this.schema = specification.schema.getAllElementsByName(child.nodeName)[0];
                 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
                 for (var i in attributeMap) {
                     if (isNaN(Number(i)) == true) {
@@ -226,6 +227,15 @@
                             break;
                     }
                 }
+                if (child.nodeName == 'surveyentry') {
+                    console.log("NOTE - Use of <surveyelement> is now deprecated. Whilst these will still work, newer nodes and tighter error checking will not be enforced");
+                    console.log("Please use the newer, type specifc nodes");
+                    if (!this.type) {
+                        throw ("Type not specified");
+                    }
+                } else {
+                    this.type = child.nodeName.split('survey')[1];
+                }
                 this.statement = child.getElementsByTagName('statement')[0].textContent;
                 if (this.type == "checkbox" || this.type == "radio") {
                     var children = child.getElementsByTagName('option');
@@ -242,6 +252,17 @@
                             });
                         }
                     }
+                } else if (this.type == "slider") {
+                    this.leftText = "";
+                    this.rightText = "";
+                    var minText = child.getElementsByTagName("minText");
+                    var maxText = child.getElementsByTagName("maxText");
+                    if (minText.length > 0) {
+                        this.leftText = minText[0].textContent;
+                    }
+                    if (maxText.length > 0) {
+                        this.rightText = maxText[0].textContent;
+                    }
                 }
                 var conditionElements = child.getElementsByTagName("conditional");
                 for (var i = 0; i < conditionElements.length; i++) {
@@ -257,8 +278,7 @@
             };
 
             this.exportXML = function (doc) {
-                var node = doc.createElement('surveyentry');
-                node.setAttribute('type', this.type);
+                var node = doc.createElement('survey' + this.type);
                 var statement = doc.createElement('statement');
                 statement.textContent = this.statement;
                 node.appendChild(statement);
@@ -275,6 +295,16 @@
                 }
                 switch (this.type) {
                     case "checkbox":
+                        if (this.min != undefined) {
+                            node.setAttribute("min", this.min);
+                        } else {
+                            node.setAttribute("min", "0");
+                        }
+                        if (this.max != undefined) {
+                            node.setAttribute("max", this.max);
+                        } else {
+                            node.setAttribute("max", "undefined");
+                        }
                     case "radio":
                         for (var i = 0; i < this.options.length; i++) {
                             var option = this.options[i];
@@ -283,6 +313,7 @@
                             optionNode.textContent = option.text;
                             node.appendChild(optionNode);
                         }
+                        break;
                     case "number":
                         if (this.min != undefined) {
                             node.setAttribute("min", this.min);
@@ -290,6 +321,7 @@
                         if (this.max != undefined) {
                             node.setAttribute("max", this.max);
                         }
+                        break;
                     case "question":
                         if (this.boxsize != undefined) {
                             node.setAttribute("boxsize", this.boxsize);
@@ -297,6 +329,29 @@
                         if (this.mandatory != undefined) {
                             node.setAttribute("mandatory", this.mandatory);
                         }
+                        break;
+                    case "video":
+                        if (this.mandatory != undefined) {
+                            node.setAttribute("mandatory", this.mandatory);
+                        }
+                    case "youtube":
+                        if (this.url != undefined) {
+                            node.setAttribute("url", this.url);
+                        }
+                        break;
+                    case "slider":
+                        node.setAttribute("min", this.min);
+                        node.setAttribute("max", this.max);
+                        if (this.leftText) {
+                            var minText = doc.createElement("minText");
+                            minText.textContent = this.leftText;
+                            node.appendChild(minText);
+                        }
+                        if (this.rightText) {
+                            var maxText = doc.createElement("maxText");
+                            maxText.textContent = this.rightText;
+                            node.appendChild(maxText);
+                        }
                     default:
                         break;
                 }
@@ -319,11 +374,12 @@
             } else if (this.location == 'after') {
                 this.location = 'post';
             }
-            var children = xml.getAllElementsByTagName('surveyentry');
-            for (var i = 0; i < children.length; i++) {
+            var child = xml.firstElementChild
+            while (child) {
                 var node = new this.OptionNode(this.specification);
-                node.decode(parent, children[i]);
+                node.decode(parent, child);
                 this.options.push(node);
+                child = child.nextElementSibling;
             }
             if (this.options.length == 0) {
                 console.log("Empty survey node");
@@ -459,10 +515,12 @@
         this.outsideReference = null;
         this.loudness = null;
         this.label = null;
+        this.labelStart = "";
         this.preTest = null;
         this.postTest = null;
         this.interfaces = [];
         this.playOne = null;
+        this.restrictMovement = null;
         this.commentBoxPrefix = "Comment on track";
         this.audioElements = [];
         this.commentQuestions = [];
@@ -539,11 +597,16 @@
             }
 
             // Now decode the commentquestions
-            var commentQuestions = xml.getElementsByTagName('commentquestion');
-            for (var i = 0; i < commentQuestions.length; i++) {
-                var node = new this.commentQuestionNode(this.specification);
-                node.decode(parent, commentQuestions[i]);
-                this.commentQuestions.push(node);
+            var cqNode = xml.getElementsByTagName('commentquestions');
+            if (cqNode.length != 0) {
+                cqNode = cqNode[0];
+                var commentQuestion = cqNode.firstElementChild;
+                while (commentQuestion) {
+                    var node = new this.commentQuestionNode(this.specification);
+                    node.decode(parent, commentQuestion);
+                    this.commentQuestions.push(node);
+                    commentQuestion = commentQuestion.nextElementSibling;
+                }
             }
         };
 
@@ -589,26 +652,85 @@
             this.id = null;
             this.name = undefined;
             this.type = undefined;
-            this.options = [];
             this.statement = undefined;
             this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
             this.decode = function (parent, xml) {
                 this.id = xml.id;
                 this.name = xml.getAttribute('name');
-                this.type = xml.getAttribute('type');
+                switch (xml.nodeName) {
+                    case "commentradio":
+                        this.type = "radio";
+                        this.options = [];
+                        break;
+                    case "commentcheckbox":
+                        this.type = "checkbox";
+                        this.options = [];
+                        break;
+                    case "commentslider":
+                        this.type = "slider";
+                        this.min = undefined;
+                        this.max = undefined;
+                        this.step = undefined;
+                        break;
+                    case "commentquestion":
+                    default:
+                        this.type = "question";
+                        break;
+                }
                 this.statement = xml.getElementsByTagName('statement')[0].textContent;
-                var optNodes = xml.getElementsByTagName('option');
-                for (var i = 0; i < optNodes.length; i++) {
-                    var optNode = optNodes[i];
-                    this.options.push({
-                        name: optNode.getAttribute('name'),
-                        text: optNode.textContent
-                    });
+                if (this.type == "radio" || this.type == "checkbox") {
+                    var optNodes = xml.getElementsByTagName('option');
+                    for (var i = 0; i < optNodes.length; i++) {
+                        var optNode = optNodes[i];
+                        this.options.push({
+                            name: optNode.getAttribute('name'),
+                            text: optNode.textContent
+                        });
+                    }
+                }
+                if (this.type == "slider") {
+                    this.min = Number(xml.getAttribute("min"));
+                    this.max = Number(xml.getAttribute("max"));
+                    this.step = Number(xml.getAttribute("step"));
+                    if (this.step == undefined) {
+                        this.step = 1;
+                    }
+                    this.value = Number(xml.getAttribute("value"));
+                    if (this.value == undefined) {
+                        this.value = min;
+                    }
+                    this.leftText = xml.getElementsByTagName("minText");
+                    if (this.leftText && this.leftText.length > 0) {
+                        this.leftText = this.leftText[0].textContent;
+                    } else {
+                        this.leftText = "";
+                    }
+                    this.rightText = xml.getElementsByTagName("maxText");
+                    if (this.rightText && this.rightText.length > 0) {
+                        this.rightText = this.rightText[0].textContent;
+                    } else {
+                        this.rightText = "";
+                    }
                 }
             };
 
             this.encode = function (root) {
-                var node = root.createElement("commentquestion");
+                var node;
+                switch (this.type) {
+                    case "radio":
+                        node = root.createElement("commentradio");
+                        break;
+                    case "checkbox":
+                        node = root.createElement("commentcheckbox");
+                        break;
+                    case "slider":
+                        node = root.createElement("commentslider");
+                        break;
+                    case "question":
+                    default:
+                        node = root.createElement("commentquestion");
+                        break;
+                }
                 node.id = this.id;
                 node.setAttribute("type", this.type);
                 if (this.name != undefined) {
@@ -617,11 +739,33 @@
                 var statement = root.createElement("statement");
                 statement.textContent = this.statement;
                 node.appendChild(statement);
-                for (var option of this.options) {
-                    var child = root.createElement("option");
-                    child.setAttribute("name", option.name);
-                    child.textContent = option.text;
-                    node.appendChild(child);
+                if (this.type == "radio" || this.type == "checkbox") {
+                    for (var option of this.options) {
+                        var child = root.createElement("option");
+                        child.setAttribute("name", option.name);
+                        child.textContent = option.text;
+                        node.appendChild(child);
+                    }
+                }
+                if (this.type == "slider") {
+                    node.setAttribute("min", this.min);
+                    node.setAttribute("max", this.max);
+                    if (this.step !== 1) {
+                        node.setAttribute("step", this.step);
+                    }
+                    if (this.value !== this.min) {
+                        node.setAttribute("value", this.value);
+                    }
+                    if (this.leftText.length > 0) {
+                        var leftText = root.createElement("minText");
+                        leftText.textContent = this.leftText;
+                        node.appendChild(leftText);
+                    }
+                    if (this.rightText.length > 0) {
+                        var rightText = root.createElement("maxText");
+                        rightText.textContent = this.rightText;
+                        node.appendChild(rightText);
+                    }
                 }
                 return node;
             };
@@ -639,6 +783,8 @@
             this.label = null;
             this.startTime = undefined;
             this.stopTime = undefined;
+            this.sampleRate = undefined;
+            this.alternatives = [];
             this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
             this.parent = null;
             this.decode = function (parent, xml) {
@@ -658,6 +804,17 @@
                             break;
                     }
                 }
+                // Get the alternative nodes
+                var child = xml.firstElementChild;
+                while (child) {
+                    if (child.nodeName == "alternative") {
+                        this.alternatives.push({
+                            'url': child.getAttribute("url"),
+                            'sampleRate': child.getAttribute("sampleRate")
+                        });
+                    }
+                    child = child.nextElementSibling;
+                }
 
             };
             this.encode = function (root) {
@@ -672,6 +829,12 @@
                         eval("AENode.setAttribute('" + name + "',this." + name + ")");
                     }
                 }
+                this.alternatives.forEach(function (alt) {
+                    var node = root.createElement("alternative");
+                    node.setAttribute("url", alt.url);
+                    node.setAttribute("sampleRate", alt.sampleRate);
+                    AENode.appendChild(node);
+                });
                 return AENode;
             };
         };
--- a/php/requestKey.php	Thu Jan 19 17:20:44 2017 +0000
+++ b/php/requestKey.php	Mon Feb 13 12:20:48 2017 +0000
@@ -16,6 +16,12 @@
 header("Cache-Control: post-check=0, pre-check=0", false);
 header("Pragma: no-cache");
 
+// Get the current test URL
+$testURL = "";
+if (isset($_GET['url'])) {
+    $testURL = "../".$_GET["url"];
+}
+
 $saves = glob("../saves/*.xml");
 
 $key = "";
@@ -44,18 +50,26 @@
 $filename = "../saves/save-".$key.".xml";
 $fileHandle = fopen($filename, 'w');
 if ($fileHandle == FALSE) {
-    echo "<response><state>ERROR</state><key>".$key."</key><message>Could not open file for writing</message></response>";
-    return;
+    die("<response><state>ERROR</state><key>".$key."</key><message>Could not open file for writing</message></response>");
 }
 fclose($fileHandle);
 // TODO:
 //  Generate the XML Base file and save it
-$doc_struct = new SimpleXMLElement('<waetresult/>');
-$doc_struct->addAttribute("key",$key);
+$doc_struct = new DOMDocument;
+$doc_struct->preserveWhiteSpace = false;
+$doc_struct->formatOutput = true;
+$doc_struct->loadXML("<waetresult/>");
+// Add the root
+if (file_exists($testURL)) {
+    $test_proto_doc = new DOMDocument;
+    $test_proto_doc->loadXML(file_get_contents($testURL, FILE_TEXT));
+    $test_proto = $test_proto_doc->documentElement;
+    $test_proto = $doc_struct->importNode($test_proto, true);
+    $doc_struct->documentElement->appendChild($test_proto);
+}
 //  Add start time
 //  Add IP Address information
 //  Save the file
-$doc_struct->asXML($filename);
+$doc_struct->save($filename);
 echo "<response><state>OK</state><key>".$key."</key></response>";
-return;
 ?>
--- a/php/save.php	Thu Jan 19 17:20:44 2017 +0000
+++ b/php/save.php	Mon Feb 13 12:20:48 2017 +0000
@@ -1,38 +1,133 @@
 <?php
-    header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
-    header("Cache-Control: post-check=0, pre-check=0", false);
-    header("Pragma: no-cache");
-	error_reporting(0);
-	$saveFilenamePrefix = isset($_GET['saveFilenamePrefix']) ? $_GET['saveFilenamePrefix'].'-' : '';
-	header('Access-Control-Allow-Origin: *');
-	header("Content-type: text/xml");
-	$postText = file_get_contents('php://input');
-	$file_key = $_GET['key'];
-	$filename = '../saves/'.$saveFilenamePrefix.'save-'.$file_key.".xml";
-    $doc = new DOMDocument;
-    $doc->preserveWhiteSpace = false;
-    $doc->formatOutput = true;
-    $doc->loadXML($postText);
-    $postText = $doc->saveXML();
-	$fileHandle = fopen($filename, 'w');
-	if ($fileHandle == FALSE)
-	{
-		// Filehandle failed
-		$xml = '<response state="error"><message>Could not open file</message></response>';
-		echo $xml;
-		return;
-	}
-	$wbytes = fwrite($fileHandle, $postText);
-	if ($wbytes === FALSE)
-	{
-		// FileWrite failed
-		$xml = '<response state="error"><message>Could not write file "'.$filename.'"</message></response>';
-		echo $xml;
-		return;
-	}
-	fclose($fileHandle);
-	
-	// Return XML confirmation data
-	$xml = '<response state="OK"><message>OK</message><file bytes="'.$wbytes.'">"'.$filename.'"</file></response>';
-	echo $xml;
+
+function findNodeByAttribute($nodeList, $attributeName, $attributeValue) {
+    if (empty($attributeName) || empty($attributeValue)) {
+        die("Error: Empty findNodeByAttribute");
+    }
+    if (empty($nodeList)) {
+        return 0;
+    }
+    foreach($nodeList as $item) {
+        if ($item->hasAttribute($attributeName)) {
+            if ($item->getAttribute($attributeValue) == $attributeValue) {
+                return $item;
+            }
+        }
+    }
+    return 0;
+}
+
+// Set the response headers
+header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
+header("Pragma: no-cache");
+header('Access-Control-Allow-Origin: *');
+header("Content-type: text/xml");
+//error_reporting(0);
+
+// Load up the parameters
+$saveFilenamePrefix = '';
+if (isset($_GET['saveFilenamePrefix'])) {
+    $saveFilenamePrefix = $_GET['saveFilenamePrefix'].'-';
+}
+$postText = file_get_contents('php://input');
+$file_key = $_GET['key'];
+$filename = '../saves/'.$saveFilenamePrefix.'save-'.$file_key.".xml";
+
+if (!file_exists($filename)) {
+    die('<response state="error"><message>Could not find save</message></response>');
+}
+
+// Open the save
+$saved_doc = new DOMDocument;
+$saved_doc->preserveWhiteSpace = false;
+$saved_doc->formatOutput = true;
+$saved_doc->loadXML(file_get_contents($filename, FILE_TEXT));
+$saved_root = $saved_doc->documentElement;
+
+// Construct the XML document into a new tree
+$doc = new DOMDocument;
+$doc->loadXML($postText);
+$docRoot = $doc->documentElement;
+
+// Add the relavent nodes:
+// <datetime>
+$n1 = $docRoot->getElementsByTagName("datetime");
+$n2 = $saved_root->getElementsByTagName("datetime");
+if ($n1->length > 0 && $n2->length == 0) {
+    $n1 = $doc->importNode($n1->item(0), true);
+    $docRoot->appendChild($n1);
+}
+
+//<navigator>
+$n1 = $docRoot->getElementsByTagName("navigator");
+$n2 = $saved_root->getElementsByTagName("navigator");
+if ($n1->length > 0 && $n2->length == 0) {
+    $n1 = $doc->importNode($n1->item(0), true);
+    $docRoot->appendChild($n1);
+}
+
+//<survey location="pre">
+$n1 = $docRoot->getElementsByTagName("survey");
+$n2 = $saved_root->getElementsByTagName("survey");
+if ($n1->length > 0) {
+    // Check if in save
+    if ($n2->length == 0) {
+        $n2 = 0;
+    }
+    $sn1 = findNodeByAttribute($n1, "location", "pre");
+    $sn2 = findNodeByAttribute($n2, "location", "pre");
+    if ($sn1 != 0) {
+        if ($sn2 != 0 && $sn2.getAttribute("state") != "complete") {
+            $saved_root->removeChild($sn2);
+            $sn2 = 0;
+        }
+        if ($sn2 == 0) {
+            $sn1 = $doc->importNode($sn1->item(0), true);
+            $docRoot->appendChild($sn1);
+        }
+    }
+    
+    $sn1 = findNodeByAttribute($n1, "location", "post");
+    $sn2 = findNodeByAttribute($n2, "location", "post");
+    if ($sn1 != 0) {
+        if ($sn2 != 0 && $sn2.getAttribute("state") != "complete") {
+            $saved_root->removeChild($sn2);
+            $sn2 = 0;
+        }
+        if ($sn2 == 0) {
+            $sn1 = $doc->importNode($sn1->item(0), true);
+            $docRoot->appendChild($sn1);
+        }
+    }
+}
+
+//<page ref="">
+$n1 = $docRoot->getElementsByTagName("page");
+$n2 = $saved_root->getElementsByTagName("page");
+if ($n1->length > 0) {
+    if ($n2->length == 0) {
+        $n2 = 0;
+    }
+    foreach($n1 as $page) {
+        $ref = $page->getAttribute("ref");
+        if (!empty($ref)) {
+            $pn2 = findNodeByAttribute($n2, "ref", $ref);
+            if ($pn2 != 0 && $pn2.getAttribute("state") != "complete") {
+                $saved_root->removeChild($pn2);
+                $pn2 = 0;
+            }
+            if ($pn2 == 0) {
+                $pn1 = $doc->importNode($page, true);
+                $docRoot->appendChild($pn1);
+            }
+        }
+    }
+}
+
+// Iterate through new doc
+$wbytes = $doc->save($filename);
+
+// Return XML confirmation data
+$xml = '<response state="OK"><message>OK</message><file bytes="'.$wbytes.'">"'.$filename.'"</file></response>';
+echo $xml;
 ?>
--- a/python/generate_report.py	Thu Jan 19 17:20:44 2017 +0000
+++ b/python/generate_report.py	Mon Feb 13 12:20:48 2017 +0000
@@ -120,9 +120,10 @@
 
 # generate images for later use
 if render_figures:
-    subprocess.call("python timeline_view_movement.py '"+folder_name+"'", shell=True)
-    subprocess.call("python score_parser.py '"+folder_name+"'", shell=True)
-    subprocess.call("python score_plot.py '"+folder_name+"ratings/'", shell=True)
+    script_path = os.path.dirname(os.path.realpath(__file__)) # where is generate_report.py?
+    subprocess.call("python " +script_path+"/timeline_view_movement.py '"+folder_name+"'", shell=True)
+    subprocess.call("python " +script_path+"/score_parser.py '"+folder_name+"'", shell=True)
+    subprocess.call("python " +script_path+"/score_plot.py '"+folder_name+"ratings/'", shell=True)
 
 # make array of text and array of dates
 body_array = []
--- a/python/timeline_view_movement.py	Thu Jan 19 17:20:44 2017 +0000
+++ b/python/timeline_view_movement.py	Mon Feb 13 12:20:48 2017 +0000
@@ -124,9 +124,10 @@
                     stop_times_global  = []
                     listen_events = audioelement.findall("./metric/metricresult/[@name='elementListenTracker']/event")
                     for event in listen_events:
-                        # get testtime: start and stop
-                        start_times_global.append(float(event.find('testtime').get('start')))#-time_offset)
-                        stop_times_global.append(float(event.find('testtime').get('stop')))#-time_offset)
+                        if event.find('testtime') is not None:
+                            # get testtime: start and stop
+                            start_times_global.append(float(event.find('testtime').get('start')))#-time_offset)
+                            stop_times_global.append(float(event.find('testtime').get('stop')))#-time_offset)
                     
                     # display fragment name at start
                     plt.text(0,initial_position+0.02,audio_id,color=colormap[increment%len(colormap)]) #,rotation=45
@@ -289,12 +290,35 @@
                 plt.ylabel('Rating') # default
                 plt.ylim(0, 1) # rating between 0 and 1
 
-                # TO DO: 
-                # Y axis title and tick labels as specified in 'setup' for corresponding page
+                # Y axis title and tick labels as specified in 'setup'
+                # for corresponding page
+                page_setup = root.find("./waet/page[@id='"+page_name+"']") 
+                    # 'ref' of page is 'id' in page setup
+
                 # Different plots for different axes
+                interfaces = page_setup.findall("./interface")
+                interface_title = interfaces[0].find("./title")
+                scales = interfaces[0].findall("./scales") # get first interface by default
+                scalelabels = scales[0].findall("./scalelabel") # get first scale by default
+
+                labelpos = [] # array of scalelabel positions
+                labelstr = [] # array of strings at labels
+                for scalelabel in scalelabels:
+                    labelpos.append(float(scalelabel.get('position'))/100.0)
+                    labelstr.append(scalelabel.text)
+
+                # use interface name as Y axis label
+                if interface_title is not None:
+                    plt.ylabel(interface_title.text)
+                else:
+                    plt.ylabel('Rating') # default
+
+                if len(labelpos):
+                    plt.yticks(labelpos, labelstr)
             
                 #plt.show() # uncomment to show plot; comment when just saving
                 #exit()
             
+                # save as PDF
                 plt.savefig(timeline_folder+subject_id+"-"+page_name+".pdf", bbox_inches='tight')
                 plt.close()
--- a/test.html	Thu Jan 19 17:20:44 2017 +0000
+++ b/test.html	Mon Feb 13 12:20:48 2017 +0000
@@ -17,6 +17,7 @@
     <link rel='stylesheet' type='text/css' href='css/core.css'>
     <!-- Use jQuery hosted from Google CDN -->
     <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>-->
+    <script type="text/javascript" src="https://cdn.rawgit.com/showdownjs/showdown/1.6.0/dist/showdown.min.js"></script>
     <script type="text/javascript" src="js/jquery-2.1.4.js"></script>
     <script type="text/javascript" src="js/loader.js"></script>
 </head>
@@ -29,9 +30,7 @@
     </div>
     <div id="popupHolder" class="popupHolder" style="visibility: hidden">
         <div id="popupContent">
-            <div id="popupTitleHolder">
-                <span id="popupTitle"></span>
-            </div>
+            <div id="popupTitleHolder"></div>
             <div id="popupResponse"></div>
         </div>
         <button id="popup-proceed" class="popupButton">Next</button>
--- a/test_create/attributes.json	Thu Jan 19 17:20:44 2017 +0000
+++ b/test_create/attributes.json	Mon Feb 13 12:20:48 2017 +0000
@@ -27,5 +27,9 @@
     "postSilence": "Post Silence",
     "poolSize": "Pool Size",
     "alwaysInclude": "Always Include",
-    "crossFade": "Cross Fade"
+    "crossFade": "Cross Fade",
+    "check": "Check",
+    "value": "Value",
+    "jumpToOnPass": "Jump To ID On Pass",
+    "jumpToOnFail": "Jump To ID On Fail"
 }
--- a/test_create/test_core.js	Thu Jan 19 17:20:44 2017 +0000
+++ b/test_create/test_core.js	Mon Feb 13 12:20:48 2017 +0000
@@ -975,7 +975,7 @@
                 handleEvent: function (event) {
                     this.parent.scaleRoot.scales = [];
                     var protoScale = interfaceSpecs.getAllElementsByTagName('scaledefinitions')[0].getAllElementsByName(event.currentTarget.value)[0];
-                    var protoMarkers = protoScale.getElementsByTagName("scale");
+                    var protoMarkers = protoScale.getElementsByTagName("scalelabel");
                     for (var i = 0; i < protoMarkers.length; i++) {
                         var marker = {
                             position: protoMarkers[i].getAttribute("position"),
@@ -992,6 +992,22 @@
             optionHolder.className = 'node';
             optionHolder.id = 'popup-option-holder';
             this.content.appendChild(optionHolder);
+            this.addMarker = {
+                root: document.createElement("button"),
+                parent: this,
+                handleEvent: function () {
+                    var marker = {
+                        position: 0,
+                        text: "text"
+                    };
+                    this.parent.scaleRoot.scales.push(marker);
+                    var markerNode = new this.parent.buildMarkerNode(this.parent, marker);
+                    document.getElementById("popup-option-holder").appendChild(markerNode.root);
+                    this.parent.markerNodes.push(markerNode);
+                }
+            };
+            this.addMarker.root.textContent = "Add Marker";
+            this.addMarker.root.addEventListener("click", this.addMarker);
             this.generate = function (scaleRoot, parent) {
                 this.scaleRoot = scaleRoot;
                 this.parent = parent;
@@ -1007,23 +1023,6 @@
                     selectOption.textContent = scaleName;
                     this.preset.input.appendChild(selectOption);
                 }
-
-                this.addMarker = {
-                    root: document.createElement("button"),
-                    parent: this,
-                    handleEvent: function () {
-                        var marker = {
-                            position: 0,
-                            text: "text"
-                        };
-                        this.parent.scaleRoot.scales.push(marker);
-                        var markerNode = new this.parent.buildMarkerNode(this.parent, marker);
-                        document.getElementById("popup-option-holder").appendChild(markerNode.root);
-                        this.parent.markerNodes.push(markerNode);
-                    }
-                };
-                this.addMarker.root.textContent = "Add Marker";
-                this.addMarker.root.addEventListener("click", this.addMarker);
                 this.content.appendChild(this.addMarker.root);
 
                 // Create Marker List
--- a/tests/examples/APE_example.xml	Thu Jan 19 17:20:44 2017 +0000
+++ b/tests/examples/APE_example.xml	Mon Feb 13 12:20:48 2017 +0000
@@ -6,7 +6,7 @@
                     <statement>Please enter your name.</statement>
                     <conditional check="equals" value="John" jumpToOnPass="test-intro" jumpToOnFail="checkboxtest" />
                 </surveyentry>
-                <surveyentry type="checkbox" id="checkboxtest" mandatory="true">
+                <surveyentry type="checkbox" id="checkboxtest" mandatory="true" min="2" max="4">
                     <statement>Please select with which activities you have any experience (example checkbox question)</statement>
                     <option name="musician">Playing a musical instrument</option>
                     <option name="soundengineer">Recording or mixing audio</option>
@@ -111,24 +111,26 @@
             <audioelement url="4.wav" gain="0.0" id="track-9" />
             <audioelement url="5.wav" gain="0.0" id="track-10" />
             <audioelement url="6.wav" gain="0.0" id="track-11" type="outside-reference" />
-            <commentquestion id='mixingExperience' type="question">
-                <statement>What is your general experience with numbers?</statement>
-            </commentquestion>
-            <commentquestion id="preference" type="radio">
-                <statement>Please enter your overall preference</statement>
-                <option name="worst">Very Bad</option>
-                <option name="bad"></option>
-                <option name="OK">OK</option>
-                <option name="Good"></option>
-                <option name="Great">Great</option>
-            </commentquestion>
-            <commentquestion id="character" type="checkbox">
-                <statement>Please describe the overall character</statement>
-                <option name="funky">Funky</option>
-                <option name="mellow">Mellow</option>
-                <option name="laidback">Laid back</option>
-                <option name="heavy">Heavy</option>
-            </commentquestion>
+            <commentquestions>
+                <commentquestion id='mixingExperience'>
+                    <statement>What is your general experience with numbers?</statement>
+                </commentquestion>
+                <commentradio id="preference">
+                    <statement>Please enter your overall preference</statement>
+                    <option name="worst">Very Bad</option>
+                    <option name="bad"></option>
+                    <option name="OK">OK</option>
+                    <option name="Good"></option>
+                    <option name="Great">Great</option>
+                </commentradio>
+                <commentcheckbox id="character" type="checkbox">
+                    <statement>Please describe the overall character</statement>
+                    <option name="funky">Funky</option>
+                    <option name="mellow">Mellow</option>
+                    <option name="laidback">Laid back</option>
+                    <option name="heavy">Heavy</option>
+                </commentcheckbox>
+            </commentquestions>
             <survey location="before">
                 <surveyentry type="statement" id="test-1-intro">
                     <statement>Example of an 'APE' style interface with hidden anchor 'zero' (which needs to be below 20%), looping of the samples, randomisation of marker labels, mandatory moving of every sample, and a forced scale usage of at least 25%-75%.</statement>
--- a/tests/examples/horizontal_example.xml	Thu Jan 19 17:20:44 2017 +0000
+++ b/tests/examples/horizontal_example.xml	Mon Feb 13 12:20:48 2017 +0000
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" ?>
     <waet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="test-schema.xsd">
-        <setup interface="ABC" projectReturn="save.php">
+        <setup interface="horizontal" projectReturn="save.php">
             <metric>
                 <metricenable>testTimer</metricenable>
                 <metricenable>elementTimer</metricenable>
--- a/tests/examples/mushra_example.xml	Thu Jan 19 17:20:44 2017 +0000
+++ b/tests/examples/mushra_example.xml	Mon Feb 13 12:20:48 2017 +0000
@@ -51,9 +51,18 @@
                 <interfaceoption type="show" name='playhead' />
                 <interfaceoption type="show" name="page-count" />
                 <interfaceoption type="show" name="volume" />
+                <scales>
+                    <scalelabel position="12">Much Worse</scalelabel>
+                    <scalelabel position="25">Worse</scalelabel>
+                    <scalelabel position="38">Slightly Worse</scalelabel>
+                    <scalelabel position="50">About the same</scalelabel>
+                    <scalelabel position="62">Slightly Better</scalelabel>
+                    <scalelabel position="75">Better</scalelabel>
+                    <scalelabel position="88">Much Better</scalelabel>
+                </scales>
             </interface>
         </setup>
-        <page id='test-0' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='true' loudness="-12">
+        <page id='test-0' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='true' loudness="-12" restrictMovement="true">
             <commentboxprefix>Comment on fragment</commentboxprefix>
             <interface>
                 <scales>
@@ -82,7 +91,7 @@
                 </surveyentry>
             </survey>
         </page>
-        <page id='test-1' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='false'>
+        <page id='test-1' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='false' restrictMovement="true">
             <commentboxprefix>Comment on fragment</commentboxprefix>
             <interface name="preference">
                 <title>Example Test Question</title>
@@ -101,24 +110,26 @@
             <audioelement url="4.wav" gain="0.0" id="track-9" />
             <audioelement url="5.wav" gain="0.0" id="track-10" />
             <audioelement url="1.wav" gain="0.0" id="track-11" type="outside-reference" />
-            <commentquestion id='mixingExperience' type="question">
-                <statement>What is your general experience with numbers?</statement>
-            </commentquestion>
-            <commentquestion id="preference" type="radio">
-                <statement>Please enter your overall preference</statement>
-                <option name="worst">Very Bad</option>
-                <option name="bad"></option>
-                <option name="OK">OK</option>
-                <option name="Good"></option>
-                <option name="Great">Great</option>
-            </commentquestion>
-            <commentquestion id="character" type="checkbox">
-                <statement>Please describe the overall character</statement>
-                <option name="funky">Funky</option>
-                <option name="mellow">Mellow</option>
-                <option name="laidback">Laid back</option>
-                <option name="heavy">Heavy</option>
-            </commentquestion>
+            <commentquestions>
+                <commentquestion id='mixingExperience' type="question">
+                    <statement>What is your general experience with numbers?</statement>
+                </commentquestion>
+                <commentquestion id="preference" type="radio">
+                    <statement>Please enter your overall preference</statement>
+                    <option name="worst">Very Bad</option>
+                    <option name="bad"></option>
+                    <option name="OK">OK</option>
+                    <option name="Good"></option>
+                    <option name="Great">Great</option>
+                </commentquestion>
+                <commentquestion id="character" type="checkbox">
+                    <statement>Please describe the overall character</statement>
+                    <option name="funky">Funky</option>
+                    <option name="mellow">Mellow</option>
+                    <option name="laidback">Laid back</option>
+                    <option name="heavy">Heavy</option>
+                </commentquestion>
+            </commentquestions>
             <survey location="before">
                 <surveyentry type="statement" id="test-1-intro">
                     <statement>Example of a 'MUSHRA' style interface with hidden anchor 'zero' (which needs to be below 20%), looping of the samples, randomisation of marker labels, mandatory moving of every sample, and a forced scale usage of at least 25%-75%.</statement>
--- a/tests/examples/radio_example.xml	Thu Jan 19 17:20:44 2017 +0000
+++ b/tests/examples/radio_example.xml	Mon Feb 13 12:20:48 2017 +0000
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" ?>
     <waet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="test-schema.xsd">
-        <setup interface="likert" projectReturn="save.php" crossFade="3.0">
+        <setup interface="discrete" projectReturn="save.php" crossFade="3.0">
             <metric>
                 <metricenable>testTimer</metricenable>
                 <metricenable>elementTimer</metricenable>
--- a/tests/examples/timeline.xml	Thu Jan 19 17:20:44 2017 +0000
+++ b/tests/examples/timeline.xml	Mon Feb 13 12:20:48 2017 +0000
@@ -32,21 +32,23 @@
             </interface>
             <audioelement url="0.wav" id="track-1" />
             <audioelement url="1.wav" id="track-2" />
-            <commentquestion id="preference" type="radio">
-                <statement>Please enter your overall preference</statement>
-                <option name="worst">Very Bad</option>
-                <option name="bad"></option>
-                <option name="OK">OK</option>
-                <option name="Good"></option>
-                <option name="Great">Great</option>
-            </commentquestion>
-            <commentquestion id="character" type="checkbox">
-                <statement>Please describe the overall character</statement>
-                <option name="funky">Funky</option>
-                <option name="mellow">Mellow</option>
-                <option name="laidback">Laid back</option>
-                <option name="heavy">Heavy</option>
-            </commentquestion>
+            <commentquestions>
+                <commentradio id="preference">
+                    <statement>Please enter your overall preference</statement>
+                    <option name="worst">Very Bad</option>
+                    <option name="bad"></option>
+                    <option name="OK">OK</option>
+                    <option name="Good"></option>
+                    <option name="Great">Great</option>
+                </commentradio>
+                <commentcheckbox id="character">
+                    <statement>Please describe the overall character</statement>
+                    <option name="funky">Funky</option>
+                    <option name="mellow">Mellow</option>
+                    <option name="laidback">Laid back</option>
+                    <option name="heavy">Heavy</option>
+                </commentcheckbox>
+            </commentquestions>
         </page>
         <page id='test-1' hostURL="media/example/" randomiseOrder='true' repeatCount='4' loop='true' loudness="-23">
             <title>My Test</title>
@@ -61,20 +63,22 @@
             </interface>
             <audioelement url="0.wav" id="track-3" />
             <audioelement url="1.wav" id="track-4" />
-            <commentquestion id="preference1" type="radio">
-                <statement>Please enter your overall preference</statement>
-                <option name="worst">Very Bad</option>
-                <option name="bad"></option>
-                <option name="OK">OK</option>
-                <option name="Good"></option>
-                <option name="Great">Great</option>
-            </commentquestion>
-            <commentquestion id="character1" type="checkbox">
-                <statement>Please describe the overall character</statement>
-                <option name="funky">Funky</option>
-                <option name="mellow">Mellow</option>
-                <option name="laidback">Laid back</option>
-                <option name="heavy">Heavy</option>
-            </commentquestion>
+            <commentquestions>
+                <commentradio id="preference1">
+                    <statement>Please enter your overall preference</statement>
+                    <option name="worst">Very Bad</option>
+                    <option name="bad"></option>
+                    <option name="OK">OK</option>
+                    <option name="Good"></option>
+                    <option name="Great">Great</option>
+                </commentradio>
+                <commentcheckbox id="character1">
+                    <statement>Please describe the overall character</statement>
+                    <option name="funky">Funky</option>
+                    <option name="mellow">Mellow</option>
+                    <option name="laidback">Laid back</option>
+                    <option name="heavy">Heavy</option>
+                </commentcheckbox>
+            </commentquestions>
         </page>
     </waet>
--- a/xml/test-schema.xsd	Thu Jan 19 17:20:44 2017 +0000
+++ b/xml/test-schema.xsd	Mon Feb 13 12:20:48 2017 +0000
@@ -75,7 +75,7 @@
                     <xs:element name="commentboxprefix" type="xs:string" minOccurs="0" maxOccurs="1" />
                     <xs:element ref="interface" minOccurs="1" maxOccurs="unbounded" />
                     <xs:element ref="audioelement" minOccurs="1" maxOccurs="unbounded" />
-                    <xs:element ref="commentquestion" minOccurs="0" maxOccurs="unbounded" />
+                    <xs:element ref="commentquestions" minOccurs="0" maxOccurs="1" />
                     <xs:element ref="survey" minOccurs="0" maxOccurs="2" />
                 </xs:sequence>
                 <xs:attribute ref="id" use="required" />
@@ -97,11 +97,13 @@
                         </xs:restriction>
                     </xs:simpleType>
                 </xs:attribute>
+                <xs:attribute name="labelStart" type="xs:string" use="optional" default="" />
                 <xs:attribute ref="poolSize" />
                 <xs:attribute ref="alwaysInclude" />
                 <xs:attribute ref="preSilence" />
                 <xs:attribute ref="postSilence" />
                 <xs:attribute ref="playOne" />
+                <xs:attribute name="restrictMovement" type="xs:boolean" default="false" use="optional" />
             </xs:complexType>
         </xs:element>
 
@@ -162,6 +164,20 @@
 
         <xs:element name="audioelement">
             <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="alternative" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:attribute name="url" type="xs:anyURI" use="required" />
+                            <xs:attribute name="sampleRate" use="optional">
+                                <xs:simpleType>
+                                    <xs:restriction base="xs:decimal">
+                                        <xs:minInclusive value="1" />
+                                    </xs:restriction>
+                                </xs:simpleType>
+                            </xs:attribute>
+                        </xs:complexType>
+                    </xs:element>
+                </xs:sequence>
                 <xs:attribute ref="id" use="required" />
                 <xs:attribute name="url" type="xs:anyURI" use="required" />
                 <xs:attribute name="gain" type="xs:decimal" default="0" />
@@ -203,13 +219,31 @@
                         </xs:restriction>
                     </xs:simpleType>
                 </xs:attribute>
+                <xs:attribute name="sampleRate" use="optional">
+                    <xs:simpleType>
+                        <xs:restriction base="xs:decimal">
+                            <xs:minInclusive value="1" />
+                        </xs:restriction>
+                    </xs:simpleType>
+                </xs:attribute>
             </xs:complexType>
         </xs:element>
 
-        <xs:element name="commentquestion">
+        <xs:element name="commentquestions">
+            <xs:complexType>
+                <xs:choice maxOccurs="unbounded">
+                    <xs:element name="commentquestion" maxOccurs="unbounded" />
+                    <xs:element name="commentradio" maxOccurs="unbounded" />
+                    <xs:element name="commentcheckbox" maxOccurs="unbounded" />
+                    <xs:element name="commentslider" maxOccurs="unbounded" />
+                </xs:choice>
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentradio">
             <xs:complexType>
                 <xs:sequence>
-                    <xs:element ref="statement" minOccurs="0" maxOccurs="1" />
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
                     <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
                         <xs:complexType>
                             <xs:simpleContent>
@@ -222,22 +256,202 @@
                 </xs:sequence>
                 <xs:attribute ref="id" use="optional" />
                 <xs:attribute ref="name" use="optional" />
-                <xs:attribute name="type" default="question">
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentcheckbox">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:simpleContent>
+                                <xs:extension base="xs:string">
+                                    <xs:attribute ref="name" />
+                                </xs:extension>
+                            </xs:simpleContent>
+                        </xs:complexType>
+                    </xs:element>
+                </xs:sequence>
+                <xs:attribute ref="id" use="optional" />
+                <xs:attribute ref="name" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentquestion">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="optional" />
+                <xs:attribute ref="name" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentslider">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="minText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                    <xs:element name="maxText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="optional" />
+                <xs:attribute ref="name" use="optional" />
+                <xs:attribute name="min" type="xs:decimal" use="required" />
+                <xs:attribute name="max" type="xs:decimal" use="required" />
+                <xs:attribute name="step" type="xs:decimal" use="optional" default="1" />
+                <xs:attribute name="value" type="xs:decimal" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="conditional">
+            <xs:complexType>
+                <xs:attribute name="check" use="required">
                     <xs:simpleType>
                         <xs:restriction base="xs:string">
-                            <xs:enumeration value="question" />
-                            <xs:enumeration value="radio" />
-                            <xs:enumeration value="checkbox" />
+                            <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:element name="surveyquestion">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="boxsize" default="normal">
+                    <xs:simpleType>
+                        <xs:restriction base="xs:string">
+                            <xs:enumeration value="normal" />
+                            <xs:enumeration value="large" />
+                            <xs:enumeration value="small" />
+                            <xs:enumeration value="huge" />
                         </xs:restriction>
                     </xs:simpleType>
                 </xs:attribute>
             </xs:complexType>
         </xs:element>
 
+        <xs:element name="surveyradio">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:simpleContent>
+                                <xs:extension base="xs:string">
+                                    <xs:attribute ref="name" />
+                                </xs:extension>
+                            </xs:simpleContent>
+                        </xs:complexType>
+                    </xs:element>
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="min" type="xs:decimal" />
+                <xs:attribute name="max" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveycheckbox">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:simpleContent>
+                                <xs:extension base="xs:string">
+                                    <xs:attribute ref="name" />
+                                </xs:extension>
+                            </xs:simpleContent>
+                        </xs:complexType>
+                    </xs:element>
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="min" type="xs:decimal" />
+                <xs:attribute name="max" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveystatement">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveynumber">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="min" type="xs:decimal" />
+                <xs:attribute name="max" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyslider">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="minText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                    <xs:element name="maxText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute name="min" use="required" type="xs:decimal" />
+                <xs:attribute name="max" use="required" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyvideo">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute name="url" use="required" type="xs:string" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyyoutube">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute name="url" use="required" type="xs:string" />
+            </xs:complexType>
+        </xs:element>
+
         <xs:element name="survey">
             <xs:complexType>
-                <xs:sequence>
-                    <xs:element name="surveyentry" minOccurs="0" maxOccurs="unbounded">
+                <xs:choice maxOccurs="unbounded">
+                    <xs:element name="surveyentry" maxOccurs="unbounded">
                         <xs:complexType>
                             <xs:sequence>
                                 <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
@@ -250,23 +464,7 @@
                                         </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:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
                             </xs:sequence>
                             <xs:attribute ref="id" use="required" />
                             <xs:attribute ref="name" />
@@ -299,7 +497,15 @@
                             <xs:attribute name="url" type="xs:string" use="optional" />
                         </xs:complexType>
                     </xs:element>
-                </xs:sequence>
+                    <xs:element name="surveyquestion" maxOccurs="unbounded" />
+                    <xs:element name="surveyradio" maxOccurs="unbounded" />
+                    <xs:element name="surveycheckbox" maxOccurs="unbounded" />
+                    <xs:element name="surveystatement" maxOccurs="unbounded" />
+                    <xs:element name="surveynumber" maxOccurs="unbounded" />
+                    <xs:element name="surveyslider" maxOccurs="unbounded" />
+                    <xs:element name="surveyvideo" maxOccurs="unbounded" />
+                    <xs:element name="surveyyoutube" maxOccurs="unbounded" />
+                </xs:choice>
                 <xs:attribute name="location">
                     <xs:simpleType>
                         <xs:restriction base="xs:string">