view interfaces/ape.js @ 1116:c44fbf72f7f2

All interfaces support comment boxes. Comment box identification matches presented tag (for instance, AB will be Comment on fragment A, rather than 1). Tighter buffer loading protocol, audioObjects register with the buffer rather than checking for buffer existence (which can be buggy depending on the buffer state). Buffers now have a state to ensure exact location in loading chain (downloading, decoding, LUFS, ready).
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Fri, 29 Jan 2016 11:11:57 +0000
parents
children f7da0ea7a4c1
line wrap: on
line source
/**
 *  ape.js
 *  Create the APE interface
 */


// Once this is loaded and parsed, begin execution
loadInterface();

function loadInterface() {
	
	// Get the dimensions of the screen available to the page
	var width = window.innerWidth;
	var height = window.innerHeight;
	
	// The injection point into the HTML page
	interfaceContext.insertPoint = document.getElementById("topLevelBody");
	var testContent = document.createElement('div');
	
	testContent.id = 'testContent';
	
	// Bindings for interfaceContext
	interfaceContext.checkAllPlayed = function()
	{
		hasBeenPlayed = audioEngineContext.checkAllPlayed();
		if (hasBeenPlayed.length > 0) // if a fragment has not been played yet
	    {
	    	str = "";
	    	if (hasBeenPlayed.length > 1) {
		    	for (var i=0; i<hasBeenPlayed.length; i++) {
		    		str = str + hasBeenPlayed[i];
		    		if (i < hasBeenPlayed.length-2){
		    			str += ", ";
		    		} else if (i == hasBeenPlayed.length-2) {
		    			str += " or ";
		    		}
		    	}
		    	alert('You have not played fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.');
	       } else {
	       		alert('You have not played fragment ' + hasBeenPlayed[0] + ' yet. Please listen, rate and comment all samples before submitting.');
	       }
	        return false;
	    }
	    return true;
	};
	
	interfaceContext.checkAllMoved = function() {
		var state = true;
		var str = 'You have not moved the following sliders. ';
		for (var i=0; i<this.interfaceSliders.length; i++)
		{
			var interfaceTID = [];
			for (var j=0; j<this.interfaceSliders[i].metrics.length; j++)
			{
				if (this.interfaceSliders[i].metrics[j].wasMoved == false)
				{
					state = false;
					interfaceTID.push(j);
				}
			}
			if (interfaceTID.length != 0)
			{
				var interfaceName = this.interfaceSliders[i].interfaceObject.title;
				if (interfaceName == undefined) {
					str += 'On axis '+String(i+1)+' you must move ';
				} else {
					str += 'On axis "'+interfaceName+'" you must move ';
				}
				if (interfaceTID.length == 1)
				{
					str += 'slider '+interfaceTID[0]+'. ';
				}
				else {
					str += 'sliders ';
					for (var k=0; k<interfaceTID.length-1; k++)
					{
						str += interfaceTID[k]+', ';
					}
					str += interfaceTID[interfaceTID.length-1] +'. ';
				}
			}
		}
		if (state != true)
		{
			alert(str);
			console.log(str);
		}
		return state;
	};
	
	Interface.prototype.checkAllCommented = function() {
		var audioObjs = audioEngineContext.audioObjects;
		var audioHolder = testState.stateMap[testState.stateIndex];
		var state = true;
		if (audioHolder.elementComments) {
			var strNums = [];
			for (var i=0; i<audioObjs.length; i++)
			{
				if (audioObjs[i].commentDOM.trackCommentBox.value.length == 0) {
					state = false;
					strNums.push(i);
				}
			}
			if (state == false) {
				if (strNums.length > 1) {
					var str = "";
			    	for (var i=0; i<strNums.length; i++) {
			    		str = str + strNums[i];
			    		if (i < strNums.length-2){
			    			str += ", ";
			    		} else if (i == strNums.length-2) {
			    			str += " or ";
			    		}
			    	}
			    	alert('You have not commented on fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.');
		       } else {
		       		alert('You have not commented on fragment ' + strNums[0] + ' yet. Please listen, rate and comment all samples before submitting.');
		       }
			}
		}
		return state;
	};
	
	Interface.prototype.checkScaleRange = function()
	{
		var audioObjs = audioEngineContext.audioObjects;
		var audioHolder = testState.stateMap[testState.stateIndex];
		var state = true;
		var str = '';
		for (var i=0; i<this.interfaceSliders.length; i++)
		{
			var minScale;
			var maxScale;
			var interfaceObject = interfaceContext.interfaceSliders[0].interfaceObject;
			for (var j=0; j<interfaceObject.options.length; j++)
			{
				if (interfaceObject.options[j].check == "scalerange") {
					minScale = interfaceObject.options[j].min;
					maxScale = interfaceObject.options[j].max;
					break;
				}
			}
			var minRanking = convSliderPosToRate(this.interfaceSliders[i].sliders[0]);
			var maxRanking = minRanking;
			for (var j=1; j<this.interfaceSliders[i].sliders.length; j++)
			{
				var ranking = convSliderPosToRate(this.interfaceSliders[i].sliders[j]);
				if (ranking < minRanking)
				{
					minRanking = ranking;
				} else if (ranking > maxRanking)
				{
					maxRanking = ranking;
				}
			}
			if (minRanking > minScale || maxRanking < maxScale)
			{
				state = false;
				str += 'On axis "'+this.interfaceSliders[i].interfaceObject.title+'" you have not used the full width of the scale. ';
			}
		}
		if (state != true)
		{
			alert(str);
			console.log(str);
		}
		return state;
	};
	
	Interface.prototype.objectSelected = null;
	Interface.prototype.objectMoved = false;
	Interface.prototype.selectObject = function(object)
	{
		if (this.objectSelected == null)
		{
			this.objectSelected = object;
			this.objectMoved = false;
		}
	};
	Interface.prototype.moveObject = function()
	{
		if (this.objectMoved == false)
		{
			this.objectMoved = true;
		}
	};
	Interface.prototype.releaseObject = function()
	{
		this.objectSelected = null;
		this.objectMoved = false;
	};
	Interface.prototype.getSelectedObject = function()
	{
		return this.objectSelected;
	};
	Interface.prototype.hasSelectedObjectMoved = function()
	{
		return this.objectMoved;
	};
	
	// Bindings for slider interfaces
	Interface.prototype.interfaceSliders = [];
	
	// Bindings for audioObjects
	
	// Create the top div for the Title element
	var titleAttr = specification.title;
	var title = document.createElement('div');
	title.className = "title";
	title.align = "center";
	var titleSpan = document.createElement('span');
	
	// Set title to that defined in XML, else set to default
	if (titleAttr != undefined) {
		titleSpan.textContent = titleAttr;
	} else {
		titleSpan.textContent =  'Listening test';
	}
	// Insert the titleSpan element into the title div element.
	title.appendChild(titleSpan);
	
	// Create Interface buttons!
	var interfaceButtons = document.createElement('div');
	interfaceButtons.id = 'interface-buttons';
	
	// Create playback start/stop points
	var playback = document.createElement("button");
	playback.innerHTML = 'Stop';
	playback.id = 'playback-button';
	// onclick function. Check if it is playing or not, call the correct function in the
	// audioEngine, change the button text to reflect the next state.
	playback.onclick = function() {
		if (audioEngineContext.status == 1) {
			audioEngineContext.stop();
			this.innerHTML = 'Stop';
            var time = audioEngineContext.timer.getTestTime();
            console.log('Stopped at ' + time); // DEBUG/SAFETY
		}
	};
	// Create Submit (save) button
	var submit = document.createElement("button");
	submit.innerHTML = 'Submit';
	submit.onclick = buttonSubmitClick;
	submit.id = 'submit-button';
	// Append the interface buttons into the interfaceButtons object.
	interfaceButtons.appendChild(playback);
	interfaceButtons.appendChild(submit);
	
	var sliderHolder = document.createElement("div");
	sliderHolder.id = "slider-holder";
	
	
	// Global parent for the comment boxes on the page
	var feedbackHolder = document.createElement('div');
	feedbackHolder.id = 'feedbackHolder';
	
	testContent.style.zIndex = 1;
	interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
	
	// Inject into HTML
	testContent.appendChild(title); // Insert the title
	testContent.appendChild(interfaceButtons);
	testContent.appendChild(sliderHolder);
	testContent.appendChild(feedbackHolder);
	interfaceContext.insertPoint.appendChild(testContent);

	// Load the full interface
	testState.initialise();
	testState.advanceState();
	
}

function loadTest(audioHolderObject)
{
	var width = window.innerWidth;
	var height = window.innerHeight;
	var id = audioHolderObject.id;
	
	interfaceContext.interfaceSliders = [];
	
	var feedbackHolder = document.getElementById('feedbackHolder');
	var sliderHolder = document.getElementById('slider-holder');
	feedbackHolder.innerHTML = null;
	sliderHolder.innerHTML = null;
	
	// Delete outside reference
	var outsideReferenceHolder = document.getElementById('outside-reference');
	if (outsideReferenceHolder != null) {
		document.getElementById('interface-buttons').removeChild(outsideReferenceHolder);
	}
	
	var interfaceObj = audioHolderObject.interfaces;
	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);
                }
            } 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 '+(audioHolderObject.presentedId+1)+' of '+specification.pages.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);
                }
            }
        }
    }
	
	var commentBoxPrefix = "Comment on fragment";
	
	var commentShow = audioHolderObject.elementComments;
	
	var loopPlayback = audioHolderObject.loop;

	currentTestHolder = document.createElement('audioHolder');
	currentTestHolder.id = audioHolderObject.id;
	currentTestHolder.repeatCount = audioHolderObject.repeatCount;
	
	// Find all the audioElements from the audioHolder
	$(audioHolderObject.audioElements).each(function(index,element){
		// Find URL of track
		// In this jQuery loop, variable 'this' holds the current audioElement.
		var audioObject = audioEngineContext.newTrack(element);
		// Check if an outside reference
		if (element.type == 'outside-reference')
		{
			// Construct outside reference;
			var orNode = new outsideReferenceDOM(audioObject,index,document.getElementById('interface-buttons'));
			audioObject.bindInterface(orNode);
		} else {
			// Create a slider per track
			var sliderNode = new sliderObject(audioObject,interfaceObj);
			audioObject.bindInterface(sliderNode);
            interfaceContext.createCommentBox(audioObject);
		}
	});
	
	// Initialse the interfaceSlider object metrics
	
	$('.track-slider').mousedown(function(event) {
		interfaceContext.selectObject($(this)[0]);
	});
	$('.track-slider').on('touchstart',null,function(event) {
		interfaceContext.selectObject($(this)[0]);
	});
	
	$('.track-slider').mousemove(function(event) {
		event.preventDefault();
	});
	
	$('.slider').mousemove(function(event) {
		event.preventDefault();
		var obj = interfaceContext.getSelectedObject();
		if (obj == null) {return;}
		$(obj).css("left",event.clientX + "px");
		interfaceContext.moveObject();
	});
	
	$('.slider').on('touchmove',null,function(event) {
		event.preventDefault();
		var obj = interfaceContext.getSelectedObject();
		if (obj == null) {return;}
		var move = event.originalEvent.targetTouches[0].clientX - 6;
		$(obj).css("left",move + "px");
		interfaceContext.moveObject();
	});

	$(document).mouseup(function(event){
		event.preventDefault();
		var obj = interfaceContext.getSelectedObject();
		if (obj == null) {return;}
		var interfaceID = obj.parentElement.getAttribute("interfaceid");
		var trackID = obj.getAttribute("trackindex");
		if (interfaceContext.hasSelectedObjectMoved() == true)
		{
			var l = $(obj).css("left");
			var id = obj.getAttribute('trackIndex');
			var time = audioEngineContext.timer.getTestTime();
			var rate = convSliderPosToRate(obj);
			audioEngineContext.audioObjects[id].metric.moved(time,rate);
			interfaceContext.interfaceSliders[interfaceID].metrics[trackID].moved(time,rate);
			console.log("slider "+id+" moved to "+rate+' ('+time+')');
		} else {
			var id = Number(obj.attributes['trackIndex'].value);
			//audioEngineContext.metric.sliderPlayed(id);
			audioEngineContext.play(id);
		}
		interfaceContext.releaseObject();
	});
	
	$('.slider').on('touchend',null,function(event){
		var obj = interfaceContext.getSelectedObject();
		if (obj == null) {return;}
		var interfaceID = obj.parentElement.getAttribute("interfaceid");
		var trackID = obj.getAttribute("trackindex");
		if (interfaceContext.hasSelectedObjectMoved() == true)
		{
			var l = $(obj).css("left");
			var id = obj.getAttribute('trackIndex');
			var time = audioEngineContext.timer.getTestTime();
			var rate = convSliderPosToRate(obj);
			audioEngineContext.audioObjects[id].metric.moved(time,rate);
			interfaceContext.interfaceSliders[interfaceID].metrics[trackID].moved(time,rate);
			console.log("slider "+id+" moved to "+rate+' ('+time+')');
		}
		interfaceContext.releaseObject();
	});
	
	
	if (audioHolderObject.showElementComments) {
		interfaceContext.showCommentBoxes(feedbackHolder,true);
	}
	
	$(audioHolderObject.commentQuestions).each(function(index,element) {
		var node = interfaceContext.createCommentQuestion(element);
		feedbackHolder.appendChild(node.holder);
	});
	
	
	//testWaitIndicator();
}

function interfaceSliderHolder(interfaceObject)
{
	this.sliders = [];
	this.metrics = [];
	this.id = document.getElementsByClassName("sliderCanvasDiv").length;
	this.name = interfaceObject.name;
	this.interfaceObject = interfaceObject;
	this.sliderDOM = document.createElement('div');
	this.sliderDOM.className = 'sliderCanvasDiv';
	this.sliderDOM.id = 'sliderCanvasHolder-'+this.id;
	
	var pagetitle = document.createElement('div');
	pagetitle.className = "pageTitle";
	pagetitle.align = "center";
	var titleSpan = document.createElement('span');
	titleSpan.id = "pageTitle-"+this.id;
	if (interfaceObject.title != undefined && typeof interfaceObject.title == "string")
	{
		titleSpan.textContent = interfaceObject.title;
	} else {
		titleSpan.textContent = "Axis "+String(this.id+1);
	}
	pagetitle.appendChild(titleSpan);
	this.sliderDOM.appendChild(pagetitle);
	
	// Create the slider box to hold the slider elements
	this.canvas = document.createElement('div');
	if (this.name != undefined)
		this.canvas.id = 'slider-'+this.name;
	else
		this.canvas.id = 'slider-'+this.id;
	this.canvas.setAttribute("interfaceid",this.id);
	this.canvas.className = 'slider';
	this.canvas.align = "left";
	this.canvas.addEventListener('dragover',function(event){
		event.preventDefault();
		event.dataTransfer.effectAllowed = 'none';
		event.dataTransfer.dropEffect = 'copy';
		return false;
	},false);
	var sliderMargin = document.createAttribute('marginsize');
	sliderMargin.nodeValue = 42; // Set default margins to 42px either side
	// Must have a known EXACT width, as this is used later to determine the ratings
	var w = (Number(sliderMargin.nodeValue)+8)*2;
	this.canvas.style.width = window.innerWidth - w +"px";
	this.canvas.style.marginLeft = sliderMargin.nodeValue +'px';
	this.canvas.setAttributeNode(sliderMargin);
	this.sliderDOM.appendChild(this.canvas);
	
	// Create the div to hold any scale objects
	this.scale = document.createElement('div');
	this.scale.className = 'sliderScale';
	this.scale.id = 'sliderScaleHolder-'+this.id;
	this.scale.align = 'left';
	this.sliderDOM.appendChild(this.scale);
	var positionScale = this.canvas.style.width.substr(0,this.canvas.style.width.length-2);
	var offset = Number(this.canvas.attributes['marginsize'].value);
	for (var scaleObj of interfaceObject.scales)
	{
		var value = document.createAttribute('value');
		var position = Number(scaleObj.position)*0.01;
		value.nodeValue = position;
		var pixelPosition = (position*positionScale)+offset;
		var scaleDOM = document.createElement('span');
		scaleDOM.textContent = scaleObj.text;
		this.scale.appendChild(scaleDOM);
		scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px';
		scaleDOM.setAttributeNode(value);
	}
	
	var dest = document.getElementById("slider-holder");
	dest.appendChild(this.sliderDOM);
	
	this.createSliderObject = function(audioObject)
	{
		var trackObj = document.createElement('div');
		trackObj.className = 'track-slider track-slider-disabled track-slider-'+audioObject.id;
		trackObj.id = 'track-slider-'+this.id+'-'+audioObject.id;
		trackObj.setAttribute('trackIndex',audioObject.id);
		if (this.name != undefined) {
			trackObj.setAttribute('interface-name',this.name);
		} else {
			trackObj.setAttribute('interface-name',this.id);
		}
		var offset = Number(this.canvas.attributes['marginsize'].value);
		// Distribute it randomnly
		var w = window.innerWidth - (offset+8)*2;
		w = Math.random()*w;
		w = Math.floor(w+(offset+8));
		trackObj.style.left = w+'px';
		this.canvas.appendChild(trackObj);
		this.sliders.push(trackObj);
		this.metrics.push(new metricTracker(this));
		trackObj.innerHTML = '<span>'+(this.metrics.length-1)+'</span>';
		this.metrics[this.metrics.length-1].initialise(convSliderPosToRate(trackObj));
		return trackObj;
	};
	
	this.resize = function(event)
	{
		var holdValues = [];
		for (var index = 0; index < this.sliders.length; index++)
		{
			holdValues.push(convSliderPosToRate(this.sliders[index])); 
		}
		var width = event.target.innerWidth;
		var sliderDiv = this.canvas;
		var sliderScaleDiv = this.scale;
		var marginsize = Number(sliderDiv.attributes['marginsize'].value);
		var w = (marginsize+8)*2;
		sliderDiv.style.width = width - w + 'px';
		var width = width - w;
		// Move sliders into new position
		for (var index = 0; index < this.sliders.length; index++)
		{
			var pos = holdValues[index];
			var pix = pos * width;
			this.sliders[index].style.left = pix+marginsize+'px';
		}
		
		// Move scale labels
		for (var index = 0; index < this.scale.children.length; index++)
		{
			var scaleObj = this.scale.children[index];
			var position = Number(scaleObj.attributes['value'].value);
			var pixelPosition = (position*width)+marginsize;
			scaleObj.style.left = Math.floor((pixelPosition-($(scaleObj).width()/2)))+'px';
		}
	};
}

function sliderObject(audioObject,interfaceObjects) {
	// Create a new slider object;
	this.parent = audioObject;
	this.trackSliderObjects = [];
	for (var i=0; i<interfaceContext.interfaceSliders.length; i++)
	{
		var trackObj = interfaceContext.interfaceSliders[i].createSliderObject(audioObject);
		this.trackSliderObjects.push(trackObj);
	}

	// Onclick, switch playback to that track
	
	this.enable = function() {
		if (this.parent.state == 1)
		{
			$(this.trackSliderObjects).each(function(i,trackObj){
				$(trackObj).removeClass('track-slider-disabled');
			});
		}
	};
	this.updateLoading = function(progress)
	{
		if (progress != 100)
		{
			progress = String(progress);
			progress = progress.split('.')[0];
			this.trackSliderObjects[0].children[0].textContent = progress+'%';
		} else {
			this.trackSliderObjects[0].children[0].textContent = this.parent.id;
		}
	};
    this.startPlayback = function()
    {
        $('.track-slider').removeClass('track-slider-playing');
        var name = ".track-slider-"+this.parent.id;
        $(name).addClass('track-slider-playing');
        $('.comment-div').removeClass('comment-box-playing');
        $('#comment-div-'+this.parent.id).addClass('comment-box-playing');
        var outsideReference = document.getElementById('outside-reference');
        if (outsideReference != undefined)
        $(outsideReference).removeClass('track-slider-playing');
    };
    this.stopPlayback = function()
    {
        var name = ".track-slider-"+this.parent.id;
        $(name).removeClass('track-slider-playing');
        $('#comment-div-'+this.parent.id).removeClass('comment-box-playing');
    };
	this.exportXMLDOM = function(audioObject) {
		// Called by the audioObject holding this element. Must be present
		var obj = [];
		$(this.trackSliderObjects).each(function(i,trackObj){
			var node = storage.document.createElement('value');
			node.setAttribute("interface-name",trackObj.getAttribute("interface-name"));
			node.textContent = convSliderPosToRate(trackObj);
			obj.push(node);
		});
		
		return obj;
	};
	this.getValue = function() {
		return convSliderPosToRate(this.trackSliderObjects[0]);
	};
	this.getPresentedId = function()
	{
		return this.trackSliderObjects[0].children[0].textContent;
	};
	this.canMove = function()
	{
		return true;
	};
}

function outsideReferenceDOM(audioObject,index,inject)
{
	this.parent = audioObject;
	this.outsideReferenceHolder = document.createElement('div');
	this.outsideReferenceHolder.id = 'outside-reference';
	this.outsideReferenceHolder.className = 'outside-reference track-slider-disabled';
	var outsideReferenceHolderspan = document.createElement('span');
	outsideReferenceHolderspan.textContent = 'Reference';
	this.outsideReferenceHolder.appendChild(outsideReferenceHolderspan);
	this.outsideReferenceHolder.setAttribute('track-id',index);
	
	this.outsideReferenceHolder.onclick = function(event)
	{
		audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
		$('.track-slider').removeClass('track-slider-playing');
        $('.comment-div').removeClass('comment-box-playing');
        if (event.currentTarget.nodeName == 'DIV') {
        	$(event.currentTarget).addClass('track-slider-playing');
        } else {
        	$(event.currentTarget.parentElement).addClass('track-slider-playing');
        }
	};
	inject.appendChild(this.outsideReferenceHolder);
	this.enable = function()
	{
		if (this.parent.state == 1)
		{
			$(this.outsideReferenceHolder).removeClass('track-slider-disabled');
		}
	};
	this.updateLoading = function(progress)
	{
		if (progress != 100)
		{
			progress = String(progress);
			progress = progress.split('.')[0];
			this.outsideReferenceHolder[0].children[0].textContent = progress+'%';
		} else {
			this.outsideReferenceHolder[0].children[0].textContent = "Play Reference";
		}
	};
    this.startPlayback = function()
    {
        $('.track-slider').removeClass('track-slider-playing');
        $(this.outsideReferenceHolder).addClass('track-slider-playing');
        $('.comment-div').removeClass('comment-box-playing');
    };
    this.stopPlayback = function()
    {
        $(this.outsideReferenceHolder).removeClass('track-slider-playing');
    };
	this.exportXMLDOM = function(audioObject)
	{
		return null;
	};
	this.getValue = function()
	{
		return 0;
	};
	this.getPresentedId = function()
	{
		return 'reference';
	};
	this.canMove = function()
	{
		return false;
	};
}

function buttonSubmitClick()
{
	var checks = [];
	checks = checks.concat(testState.currentStateMap.interfaces[0].options);
	checks = checks.concat(specification.interfaces.options);
	var canContinue = true;
	
	// Check that the anchor and reference objects are correctly placed
	if (interfaceContext.checkHiddenAnchor() == false) {return;}
	if (interfaceContext.checkHiddenReference() == false) {return;}
	
	for (var i=0; i<checks.length; i++) {
		if (checks[i].type == 'check')
		{
			switch(checks[i].name) {
			case 'fragmentPlayed':
				// Check if all fragments have been played
				var checkState = interfaceContext.checkAllPlayed();
				if (checkState == false) {canContinue = false;}
				break;
			case  'fragmentFullPlayback':
				// Check all fragments have been played to their full length
				var checkState = interfaceContext.checkFragmentsFullyPlayed();
				if (checkState == false) {canContinue = false;}
				break;
			case 'fragmentMoved':
				// Check all fragment sliders have been moved.
				var checkState = interfaceContext.checkAllMoved();
				if (checkState == false) {canContinue = false;}
				break;
			case 'fragmentComments':
				// Check all fragment sliders have been moved.
				var checkState = interfaceContext.checkAllCommented();
				if (checkState == false) {canContinue = false;}
				break;
			case 'scalerange':
				// Check the scale is used to its full width outlined by the node
				var checkState = interfaceContext.checkScaleRange();
				if (checkState == false) {canContinue = false;}
				break;
			default:
				console.log("WARNING - Check option "+checks[i].name+" is not supported on this interface");
				break;
			}

		}
		if (!canContinue) {break;}
	}
   
    if (canContinue) {
	    if (audioEngineContext.status == 1) {
	        var playback = document.getElementById('playback-button');
	        playback.click();
	    // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
	    } else
	    {
	        if (audioEngineContext.timer.testStarted == false)
	        {
	            alert('You have not started the test! Please click a fragment to begin the test!');
	            return;
	        }
	    }
	    testState.advanceState();
    } 
}

function convSliderPosToRate(trackSlider)
{
	var slider = trackSlider.parentElement;
	var w = slider.style.width;
	var marginsize = Number(slider.attributes['marginsize'].value);
	var maxPix = w.substr(0,w.length-2);
	var pix = trackSlider.style.left;
	pix = pix.substr(0,pix.length-2);
	var rate = (pix-marginsize)/maxPix;
	return rate;
}

function resizeWindow(event){
	// Function called when the window has been resized.
	// MANDATORY FUNCTION
	
	// Resize the slider objects
	for (var i=0; i<interfaceContext.interfaceSliders.length; i++)
	{
		interfaceContext.interfaceSliders[i].resize(event);
	}
}

function pageXMLSave(store, pageSpecification)
{
	// MANDATORY
	// Saves a specific test page
	// You can use this space to add any extra nodes to your XML <audioHolder> saves
	// Get the current <page> information in store (remember to appendChild your data to it)
	// pageSpecification is the current page node configuration
	// To create new XML nodes, use storage.document.createElement();
	
	if (interfaceContext.interfaceSliders.length == 1)
	{
		// If there is only one axis, there only needs to be one metric return
		return;
	}
	var audioelements = store.getElementsByTagName("audioelement");
	for (var i=0; i<audioelements.length; i++)
	{
		// Have to append the metric specific nodes
		if (pageSpecification.outsideReference == null || pageSpecification.outsideReference.id != audioelements[i].id)
		{
			var inject = audioelements[i].getElementsByTagName("metric");
			if (inject.length == 0)
			{
				inject = storage.document.createElement("metric");
			} else {
				inject = inject[0];
			}
			for (var k=0; k<interfaceContext.interfaceSliders.length; k++)
			{
				var mrnodes = interfaceContext.interfaceSliders[k].metrics[i].exportXMLDOM(inject);
				for (var j=0; j<mrnodes.length; j++)
				{
					var name = mrnodes[j].getAttribute("name");
					if (name == "elementTracker" || name == "elementTrackerFull" || name == "elementInitialPosition" || name == "elementFlagMoved")
					{
						mrnodes[j].setAttribute("interface-name",interfaceContext.interfaceSliders[k].name);
						mrnodes[j].setAttribute("interface-id",k);
						inject.appendChild(mrnodes[j]);
					}
				}
			}
		}
	}
}