Mercurial > hg > webaudioevaluationtool
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">