changeset 2848:bef3d563d667

Merge branch 'Dev_main' into vnext
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Tue, 25 Apr 2017 16:01:27 +0100
parents 7f2bb5997114 (current diff) ed2c8a04b7c4 (diff)
children 050a99108afa
files
diffstat 10 files changed, 578 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/css/core.css	Tue Apr 25 15:31:02 2017 +0100
+++ b/css/core.css	Tue Apr 25 16:01:27 2017 +0100
@@ -8,6 +8,12 @@
     margin-bottom: 10px;
     font-size: 2em;
 }
+div#footer {
+    position: fixed;
+    bottom: 0px;
+    width: 100%;
+    text-align: center;
+}
 div.indicator-box {
     position: absolute;
     left: 150px;
--- a/index.html	Tue Apr 25 15:31:02 2017 +0100
+++ b/index.html	Tue Apr 25 16:01:27 2017 +0100
@@ -24,7 +24,7 @@
 
 <body>
     <div id='topLevelBody'>
-        <h1>Web Audio Evaluation Tool</h1>
+        <h1><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.1)</a></h1>
         <h2>Start menu </h2>
         <ul>
             <li><a href="test.html?url=tests/examples/project.xml" target="_blank">APE interface test example</a></li>
@@ -51,6 +51,7 @@
         <button id="popup-previous" class="popupButton">Back</button>
     </div>
     <div class="testHalt" style="visibility: hidden"></div>
+    <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.1)</a></div>
 </body>
 
 </html>
--- a/interfaces/horizontal-sliders.css	Tue Apr 25 15:31:02 2017 +0100
+++ b/interfaces/horizontal-sliders.css	Tue Apr 25 16:01:27 2017 +0100
@@ -7,23 +7,19 @@
     /* 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;
@@ -31,29 +27,24 @@
     z-index: 3;
     margin-top: 25px;
 }
-
 div#scale-holder {
     height: inherit;
     position: absolute;
     left: 0px;
     z-index: 2;
 }
-
 div#scale-text-holder {
     position: relative;
     float: left;
 }
-
 div.scale-text {
     position: absolute;
     font-size: 1.2em;
 }
-
 canvas#scale-canvas {
     position: relative;
     float: left;
 }
-
 div.track-slider {
     float: left;
     height: 94px;
@@ -64,53 +55,44 @@
     margin-left: 94px;
     margin-bottom: 25px;
 }
-
 div.track-slider-title {
     float: left;
     padding-top: 40px;
     width: 100px;
 }
-
 button.track-slider-button {
     float: left;
     width: 100px;
     height: 94px;
 }
-
 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;
+    background-color: rgba(255, 201, 201, 0.5);
 }
-
 input.track-slider-range {
     float: left;
     margin: 2px 10px;
 }
-
 input[type=range] {
     height: 94px;
     padding: 0px;
     color: rgb(255, 144, 144);
 }
-
 input[type=range]::-webkit-slider-runnable-track {
     cursor: pointer;
     background: #fff;
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input[type=range]::-moz-range-track {
     height: 8px;
     cursor: pointer;
@@ -118,26 +100,21 @@
     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;
 }
-
 div#page-count {
     float: left;
     margin: 0px 5px;
 }
-
 div#master-volume-holder {
     position: absolute;
     top: 10px;
     left: 120px;
 }
-
 div.comment-box-playing {
     background-color: #FFDDDD;
 }
--- a/interfaces/horizontal-sliders.js	Tue Apr 25 15:31:02 2017 +0100
+++ b/interfaces/horizontal-sliders.js	Tue Apr 25 16:01:27 2017 +0100
@@ -237,6 +237,7 @@
     // An example node, you can make this however you want for each audioElement.
     // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
     // You attach them by calling audioObject.bindInterface( )
+    var playing = false;
     this.parent = audioObject;
 
     this.holder = document.createElement('div');
@@ -275,9 +276,9 @@
     this.play.onclick = function (event) {
         var id = Number(event.currentTarget.value);
         //audioEngineContext.metric.sliderPlayed(id);
-        if (event.currentTarget.getAttribute("playstate") == "ready") {
+        if (!playing) {
             audioEngineContext.play(id);
-        } else if (event.currentTarget.getAttribute("playstate") == "playing") {
+        } else if (playing) {
             audioEngineContext.stop();
         }
     };
@@ -296,7 +297,8 @@
     };
     this.startPlayback = function () {
         // Called when playback has begun
-        this.play.setAttribute("playstate", "playing");
+        playing = true;
+        this.play.textContent = "Stop";
         $(".track-slider").removeClass('track-slider-playing');
         $(this.holder).addClass('track-slider-playing');
         var outsideReference = document.getElementById('outside-reference');
@@ -310,7 +312,8 @@
     };
     this.stopPlayback = function () {
         // Called when playback has stopped. This gets called even if playback never started!
-        this.play.setAttribute("playstate", "ready");
+        playing = false;
+        this.play.textContent = "Play";
         $(this.holder).removeClass('track-slider-playing');
         var box = interfaceContext.commentBoxes.boxes.find(function (a) {
             return a.id === audioObject.id;
--- a/interfaces/interfaces.json	Tue Apr 25 15:31:02 2017 +0100
+++ b/interfaces/interfaces.json	Tue Apr 25 16:01:27 2017 +0100
@@ -30,6 +30,10 @@
             "name": "timeline",
             "scripts": ["interfaces/timeline.js"],
             "css": ["interfaces/timeline.css"]
+        }, {
+            "name": "ordinal",
+            "scripts": ["interfaces/ordinal.js"],
+            "css": ["interfaces/ordinal.css"]
         }
     ]
 }
--- a/interfaces/mushra.css	Tue Apr 25 15:31:02 2017 +0100
+++ b/interfaces/mushra.css	Tue Apr 25 16:01:27 2017 +0100
@@ -7,52 +7,43 @@
     /* 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;
@@ -62,27 +53,22 @@
     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;
+    background-color: rgba(255, 201, 201, 0.5);
 }
-
 input.track-slider-range {
     margin: 2px 0px;
 }
-
 input[type=range][orient=vertical] {
     writing-mode: bt-lr;
     /* IE */
@@ -92,7 +78,6 @@
     padding: 0 5px;
     color: rgb(255, 144, 144);
 }
-
 input[type=range]::-webkit-slider-runnable-track {
     width: 8px;
     cursor: pointer;
@@ -100,7 +85,6 @@
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input[type=range]::-moz-range-track {
     width: 8px;
     cursor: pointer;
@@ -108,79 +92,63 @@
     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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interfaces/ordinal.css	Tue Apr 25 16:01:27 2017 +0100
@@ -0,0 +1,36 @@
+[draggable] {
+    -moz-user-select: none;
+    -khtml-user-select: none;
+    -webkit-user-select: none;
+    user-select: none;
+    /* Required to make elements draggable in old WebKit */
+    -khtml-user-drag: element;
+    -webkit-user-drag: element;
+}
+.ordinal-element {
+    width: 250px;
+    height: 250px;
+    background: #bbffbb;
+    border: 2px #050 solid;
+    border-radius: 10px;
+    float: left;
+    margin: 10px 5px;
+    text-align: center;
+    cursor: move;
+}
+.disabled {
+    background-color: grey;
+}
+.playing {
+    background-color: #ffbbbb;
+    border: 2px #500 solid;
+}
+.dragging {
+    opacity: 0.4;
+}
+.over {
+    border-style: dashed;
+}
+.ordinal-element-label {
+    font-size: 2em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interfaces/ordinal.js	Tue Apr 25 16:01:27 2017 +0100
@@ -0,0 +1,487 @@
+/**
+ * WAET Blank Template
+ * Use this to start building your custom interface
+ */
+/*globals interfaceContext, window, document, specification, audioEngineContext, console, testState, $, storage */
+// Once this is loaded and parsed, begin execution
+loadInterface();
+
+function loadInterface() {
+    // Use this to do any one-time page / element construction. For instance, placing any stationary text objects,
+    // holding div's, or setting up any nodes which are present for the entire test sequence
+
+    // The injection point into the HTML page
+    interfaceContext.insertPoint = document.getElementById("topLevelBody");
+    var testContent = document.createElement('div');
+    testContent.id = 'testContent';
+
+    // Create the top div for the Title element
+    var titleAttr = specification.title;
+    var title = document.createElement('div');
+    title.className = "title";
+    title.align = "center";
+    var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
+
+    // Set title to that defined in XML, else set to default
+    if (titleAttr !== undefined) {
+        titleSpan.textContent = titleAttr;
+    } else {
+        titleSpan.textContent = 'Listening test';
+    }
+    // Insert the titleSpan element into the title div element.
+    title.appendChild(titleSpan);
+
+    var pagetitle = document.createElement('div');
+    pagetitle.className = "pageTitle";
+    pagetitle.align = "center";
+
+    titleSpan = document.createElement('span');
+    titleSpan.id = "pageTitle";
+    pagetitle.appendChild(titleSpan);
+
+    // Create Interface buttons!
+    var interfaceButtons = document.createElement('div');
+    interfaceButtons.id = 'interface-buttons';
+    interfaceButtons.style.height = '25px';
+
+    // Create playback start/stop points
+    var playback = document.createElement("button");
+    playback.innerHTML = 'Stop';
+    playback.id = 'playback-button';
+    playback.style.float = 'left';
+    // onclick function. Check if it is playing or not, call the correct function in the
+    // audioEngine, change the button text to reflect the next state.
+    playback.onclick = function () {
+        if (audioEngineContext.status == 1) {
+            audioEngineContext.stop();
+            this.innerHTML = 'Stop';
+            var time = audioEngineContext.timer.getTestTime();
+            console.log('Stopped at ' + time); // DEBUG/SAFETY
+        }
+    };
+    // Create Submit (save) button
+    var submit = document.createElement("button");
+    submit.innerHTML = 'Next';
+    submit.onclick = buttonSubmitClick;
+    submit.id = 'submit-button';
+    submit.style.float = 'left';
+    // Append the interface buttons into the interfaceButtons object.
+    interfaceButtons.appendChild(playback);
+    interfaceButtons.appendChild(submit);
+
+    // Create outside reference holder
+    var outsideRef = document.createElement("div");
+    outsideRef.id = "outside-reference-holder";
+
+    // Create a slider box
+    var slider = document.createElement("div");
+    slider.id = "slider";
+    slider.style.height = "300px";
+
+    // Global parent for the comment boxes on the page
+    var feedbackHolder = document.createElement('div');
+    feedbackHolder.id = 'feedbackHolder';
+
+    testContent.style.zIndex = 1;
+    interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema
+
+    // Inject into HTML
+    testContent.appendChild(title); // Insert the title
+    testContent.appendChild(pagetitle);
+    testContent.appendChild(interfaceButtons);
+    testContent.appendChild(outsideRef);
+    testContent.appendChild(slider);
+    testContent.appendChild(feedbackHolder);
+    interfaceContext.insertPoint.appendChild(testContent);
+
+    // Load the full interface
+    testState.initialise();
+    testState.advanceState();
+}
+
+function loadTest(page) {
+    // Called each time a new test page is to be build. The page specification node is the only item passed in
+    var id = page.id;
+
+    var feedbackHolder = document.getElementById('feedbackHolder');
+    feedbackHolder.innerHTML = "";
+
+    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");
+    }
+    interfaceObj = interfaceObj[0];
+
+    // Set the page title
+    if (typeof page.title == "string" && page.title.length > 0) {
+        document.getElementById("test-title").textContent = page.title;
+    }
+
+    if (interfaceObj.title !== null) {
+        document.getElementById("pageTitle").textContent = interfaceObj.title;
+    }
+
+    if (interfaceObj.image !== undefined) {
+        feedbackHolder.insertBefore(interfaceContext.imageHolder.root, document.getElementById("slider"));
+        interfaceContext.imageHolder.setImage(interfaceObj.image);
+    }
+    // Delete outside reference
+    document.getElementById("outside-reference-holder").innerHTML = "";
+
+    var sliderBox = document.getElementById('slider');
+    sliderBox.innerHTML = "";
+
+    var commentBoxPrefix = "Comment on track";
+    if (interfaceObj.commentBoxPrefix !== undefined) {
+        commentBoxPrefix = interfaceObj.commentBoxPrefix;
+    }
+
+    $(page.commentQuestions).each(function (index, element) {
+        var node = interfaceContext.createCommentQuestion(element);
+        feedbackHolder.appendChild(node.holder);
+    });
+
+    var index = 0;
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    page.audioElements.forEach(function (element, pageIndex) {
+        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 interfaceObject(audioObject, label);
+
+            sliderBox.appendChild(sliderObj.root);
+            audioObject.bindInterface(sliderObj);
+            interfaceContext.commentBoxes.createCommentBox(audioObject);
+            index += 1;
+        }
+    });
+    interfaceObj.options.forEach(function (option) {
+        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.appendChild(playbackHolder);
+                    }
+                    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;
+            }
+        }
+    });
+    resizeWindow();
+}
+
+function interfaceObject(audioObject, label) {
+    var container = document.getElementById("slider");
+    var playing = false;
+    var root = document.createElement("div");
+    root.className = "ordinal-element";
+    root.draggable = "true";
+    var labelElement = document.createElement("span");
+    labelElement.className = "ordinal-element-label";
+    labelElement.textContent = label;
+    root.appendChild(labelElement);
+    root.classList.add("disabled");
+    // An example node, you can make this however you want for each audioElement.
+    // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
+    // You attach them by calling audioObject.bindInterface( )
+    root.addEventListener("click", this, true);
+    root.addEventListener('dragstart', this, true);
+    root.addEventListener('dragenter', this, true);
+    root.addEventListener('dragover', this, true);
+    root.addEventListener('dragleave', this, true);
+    root.addEventListener('drop', this, true);
+    root.addEventListener('dragend', this, true);
+    this.handleEvent = function (event) {
+        if (event.type == "click") {
+            if (playing === false) {
+                audioEngineContext.play(audioObject.id);
+            } else {
+                audioEngineContext.stop();
+            }
+            playing = !playing;
+            return;
+        } else if (event.type == "dragstart") {
+            return dragStart.call(this, event);
+        } else if (event.type == "dragenter") {
+            return dragEnter.call(this, event);
+        } else if (event.type == "dragleave") {
+            return dragLeave.call(this, event);
+        } else if (event.type == "dragover") {
+            return dragOver.call(this, event);
+        } else if (event.type == "drop") {
+            return drop.call(this, event);
+        } else if (event.type == "dragend") {
+            return dragEnd.call(this, event);
+        }
+        throw (event);
+    };
+
+    function dragStart(e) {
+        e.currentTarget.classList.add("dragging");
+
+        e.dataTransfer.effectAllowed = 'move';
+        e.dataTransfer.setData('text/plain', audioObject.id);
+    }
+
+    function dragEnter(e) {
+        // this / e.target is the current hover target.
+        root.classList.add('over');
+    }
+
+    function dragLeave(e) {
+        root.classList.remove('over'); // this / e.target is previous target element.
+    }
+
+    function dragOver(e) {
+        if (e.preventDefault) {
+            e.preventDefault(); // Necessary. Allows us to drop.
+        }
+
+        e.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object.
+
+        var srcid = Number(e.dataTransfer.getData("text/plain"));
+        var elements = container.childNodes;
+        var srcObject = audioEngineContext.audioObjects.find(function (ao) {
+            return ao.id === srcid;
+        });
+        var src = srcObject.interfaceDOM.root;
+        if (src !== root) {
+            var srcpos = srcObject.interfaceDOM.getElementPosition();
+            var mypos = this.getElementPosition();
+            var neighbour;
+            if (srcpos <= mypos) {
+                neighbour = root.nextElementSibling;
+            } else {
+                neighbour = root;
+            }
+            if (neighbour)
+                container.insertBefore(src, neighbour);
+            else {
+                container.removeChild(src);
+                container.appendChild(src);
+            }
+
+        }
+
+        return false;
+    }
+
+    function drop(e) {
+        // this / e.target is current target element.
+
+        if (e.stopPropagation) {
+            e.stopPropagation(); // stops the browser from redirecting.
+        }
+        if (e.preventDefault) {
+            e.preventDefault(); // Necessary. Allows us to drop.
+        }
+
+        audioEngineContext.audioObjects.forEach(function (ao) {
+            ao.interfaceDOM.processMovement();
+        });
+
+        return false;
+    }
+
+    function dragEnd(e) {
+        // this/e.target is the source node.
+        $(".ordinal-element").removeClass("dragging");
+        $(".ordinal-element").removeClass("over");
+    }
+
+    this.getElementPosition = function () {
+        var elements = container.childNodes,
+            position = 0,
+            elem = elements[0];
+        while (root !== elem) {
+            position++;
+            elem = elem.nextElementSibling;
+        }
+        return position;
+    };
+
+    this.processMovement = function () {
+        var time = audioEngineContext.timer.getTestTime();
+        var pos = this.getElementPosition();
+        var rank = pos / (audioEngineContext.audioObjects.length - 1);
+        audioObject.metric.moved(time, rank);
+        console.log('slider ' + audioObject.id + ' moved to ' + rank + ' (' + time + ')');
+    };
+
+    this.enable = function () {
+        // This is used to tell the interface object that playback of this node is ready
+        root.classList.remove("disabled");
+        labelElement.textContent = label;
+    };
+    this.updateLoading = function (progress) {
+        // progress is a value from 0 to 100 indicating the current download state of media files
+        labelElement.textContent = String(progress);
+    };
+    this.startPlayback = function () {
+        // Called when playback has begun
+        root.classList.add("playing");
+        if (audioObject.commentDOM) {
+            audioObject.commentDOM.trackComment.classList.add("comment-box-playing");
+        }
+    };
+    this.stopPlayback = function () {
+        // Called when playback has stopped. This gets called even if playback never started!
+        root.classList.remove("playing");
+        playing = false;
+        if (audioObject.commentDOM) {
+            audioObject.commentDOM.trackComment.classList.remove("comment-box-playing");
+        }
+    };
+    this.getValue = function () {
+        // Return the current value of the object. If there is no value, return 0
+        var pos = this.getElementPosition();
+        var rank = pos / (audioEngineContext.audioObjects.length - 1);
+    };
+    this.getPresentedId = function () {
+        // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
+        return label;
+    };
+    this.canMove = function () {
+        // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale.
+        // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
+        return true;
+    };
+    this.exportXMLDOM = function (audioObject) {
+        // Called by the audioObject holding this element to export the interface <value> node.
+        // If there is no value node (such as outside reference), return null
+        // If there are multiple value nodes (such as multiple scale / 2D scales), return an array of nodes with each value node having an 'interfaceName' attribute
+        // Use storage.document.createElement('value'); to generate the XML node.
+        var node = storage.document.createElement('value');
+        node.textContent = this.getValue();
+        return node;
+
+    };
+    this.error = function () {
+        // If there is an error with the audioObject, this will be called to indicate a failure
+        root.classList.remove("disabled");
+        labelElement.textContent = "Error";
+    };
+    Object.defineProperties(this, {
+        "root": {
+            "get": function () {
+                return root;
+            },
+            "set": function () {}
+        }
+    });
+}
+
+function resizeWindow(event) {
+    // Called on every window resize event, use this to scale your page properly
+    var w = $("#slider").width();
+    var N = audioEngineContext.audioObjects.length;
+    w /= N;
+    w -= 14;
+    w = Math.floor(w);
+    $(".ordinal-element").width(w);
+}
+
+function buttonSubmitClick() // TODO: Only when all songs have been played!
+{
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
+
+    // Check that the anchor and reference objects are correctly placed
+    if (interfaceContext.checkHiddenAnchor() === false) {
+        return;
+    }
+    if (interfaceContext.checkHiddenReference() === false) {
+        return;
+    }
+
+    for (var i = 0; i < checks.length; i++) {
+        var checkState = true;
+        if (checks[i].type == 'check') {
+            switch (checks[i].name) {
+                case 'fragmentPlayed':
+                    // Check if all fragments have been played
+                    checkState = interfaceContext.checkAllPlayed(checks[i].errorMessage);
+                    break;
+                case 'fragmentFullPlayback':
+                    // Check all fragments have been played to their full length
+                    checkState = interfaceContext.checkAllPlayed(checks[i].errorMessage);
+                    console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
+                    break;
+                case 'fragmentMoved':
+                    // Check all fragment sliders have been moved.
+                    checkState = interfaceContext.checkAllMoved(checks[i].errorMessage);
+                    break;
+                case 'fragmentComments':
+                    // Check all fragment sliders have been moved.
+                    checkState = interfaceContext.checkAllCommented(checks[i].errorMessage);
+                    break;
+                case 'scalerange':
+                    // Check the scale has been used effectively
+                    checkState = interfaceContext.checkScaleRange(checks[i].errorMessage);
+
+                    break;
+                default:
+                    console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
+                    break;
+            }
+        }
+        if (checkState === false) {
+            canContinue = false;
+            break;
+        }
+    }
+
+    if (canContinue) {
+        if (audioEngineContext.status == 1) {
+            var playback = document.getElementById('playback-button');
+            playback.click();
+            // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
+        } else {
+            if (audioEngineContext.timer.testStarted === false) {
+                interfaceContext.lightbox.post("Warning", 'You have not started the test! Please press start to begin the test!');
+                return;
+            }
+        }
+        testState.advanceState();
+    }
+}
+
+function pageXMLSave(store, pageSpecification) {
+    // MANDATORY
+    // Saves a specific test page
+    // You can use this space to add any extra nodes to your XML <audioHolder> saves
+    // Get the current <page> information in store (remember to appendChild your data to it)
+    // pageSpecification is the current page node configuration
+    // To create new XML nodes, use storage.document.createElement();
+}
--- a/test.html	Tue Apr 25 15:31:02 2017 +0100
+++ b/test.html	Tue Apr 25 16:01:27 2017 +0100
@@ -37,6 +37,7 @@
         <button id="popup-previous" class="popupButton">Back</button>
     </div>
     <div class="testHalt" style="visibility: hidden"></div>
+    <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.1)</a></div>
 </body>
 
 </html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/examples/ordinal_example.xml	Tue Apr 25 16:01:27 2017 +0100
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+    <waet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="test-schema.xsd">
+        <setup interface="ordinal" projectReturn="save.php" randomiseOrder="true">
+            <metric>
+                <metricenable>testTimer</metricenable>
+                <metricenable>elementTimer</metricenable>
+                <metricenable>elementInitialPosition</metricenable>
+                <metricenable>elementTracker</metricenable>
+                <metricenable>elementFlagListenedTo</metricenable>
+                <metricenable>elementFlagMoved</metricenable>
+                <metricenable>elementListenTracker</metricenable>
+            </metric>
+            <interface>
+                <interfaceoption type="check" name="fragmentComment" />
+                <interfaceoption type="show" name='playhead' />
+                <interfaceoption type="show" name="page-count" />
+                <interfaceoption type="show" name="volume" />
+                <interfaceoption type="show" name="comments" />
+            </interface>
+        </setup>
+        <page id='test-0' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='true' loudness="-23">
+            <title>Ordinal Evaluation</title>
+            <interface></interface>
+            <audioelement url="0.wav" id="track-1" />
+            <audioelement url="1.wav" id="track-2" />
+            <audioelement url="2.wav" id="track-3" />
+            <audioelement url="3.wav" id="track-4" />
+            <audioelement url="4.wav" id="track-5" />
+            <audioelement url="5.wav" id="track-6" />
+            <audioelement url="6.wav" id="track-7" />
+            <audioelement url="7.wav" id="track-8" />
+        </page>
+    </waet>