nicholas@2479: /** nicholas@2479: * WAET Timeline nicholas@2479: * This interface plots a waveform timeline per audio fragment on a page. Clicking on the fragment will generate a comment box for processing. nicholas@2479: */ nicholas@2479: nicholas@2479: // Once this is loaded and parsed, begin execution nicholas@2479: loadInterface(); nicholas@2479: nicholas@2479: function loadInterface() { nicholas@2479: // Use this to do any one-time page / element construction. For instance, placing any stationary text objects, nicholas@2479: // holding div's, or setting up any nodes which are present for the entire test sequence nicholas@2479: nicholas@2479: interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema nicholas@2479: nicholas@2479: interfaceContext.insertPoint = document.getElementById("topLevelBody"); nicholas@2479: var testContent = document.createElement("div"); nicholas@2479: nicholas@2479: // Create the top div and Title element nicholas@2479: var title = document.createElement("div"); nicholas@2479: title.className = "title"; nicholas@2479: title.align = "center"; nicholas@2479: var titleSpan = document.createElement("span"); nicholas@2479: titleSpan.id = "test-title"; nicholas@2479: titleSpan.textContent = "Listening Test"; nicholas@2479: title.appendChild(titleSpan); nicholas@2479: nicholas@2479: var pagetitle = document.createElement("div"); nicholas@2479: pagetitle.className = "pageTitle"; nicholas@2479: pagetitle.align = "center"; nicholas@2479: titleSpan = document.createElement("span"); nicholas@2479: titleSpan.id = "page-title"; nicholas@2479: pagetitle.appendChild(titleSpan); nicholas@2479: nicholas@2479: // Create Interface buttons nicholas@2479: var interfaceButtons = document.createElement("div"); nicholas@2479: interfaceButtons.id = 'interface-buttons'; nicholas@2479: interfaceButtons.style.height = "25px"; nicholas@2479: nicholas@2479: // Create playback start/stop points nicholas@2479: var playback = document.createElement("button"); nicholas@2479: playback.innerHTML = "Stop"; nicholas@2479: playback.id = "playback-button"; nicholas@2479: playback.onclick = function() { nicholas@2479: if (audioEngineContext.status == 1) { nicholas@2479: audioEngineContext.stop(); nicholas@2479: this.innerHTML = "Stop"; nicholas@2479: var time = audioEngineContext.timer.getTestTime(); nicholas@2479: console.log("Stopped at "+time); nicholas@2479: } nicholas@2479: }; nicholas@2479: // Create Submit (save) button nicholas@2479: var submit = document.createElement("button"); nicholas@2479: submit.innerHTML = 'Next'; nicholas@2479: submit.onclick = buttonSubmitClick; nicholas@2479: submit.id = 'submit-button'; nicholas@2479: submit.style.float = 'left'; nicholas@2479: // Append the interface buttons into the interfaceButtons object. nicholas@2479: interfaceButtons.appendChild(playback); nicholas@2479: interfaceButtons.appendChild(submit); nicholas@2479: nicholas@2479: // Create outside reference holder nicholas@2479: var outsideRef = document.createElement("div"); nicholas@2479: outsideRef.id = "outside-reference-holder"; nicholas@2479: nicholas@2479: // Create content point nicholas@2479: var content = document.createElement("div"); nicholas@2479: content.id = "timeline-test-content"; nicholas@2479: nicholas@2479: //Inject nicholas@2479: testContent.appendChild(title); nicholas@2479: testContent.appendChild(pagetitle); nicholas@2479: testContent.appendChild(interfaceButtons); nicholas@2479: testContent.appendChild(outsideRef); nicholas@2479: testContent.appendChild(content); nicholas@2479: interfaceContext.insertPoint.appendChild(testContent); nicholas@2479: nicholas@2479: // Load the full interface nicholas@2479: testState.initialise(); nicholas@2479: testState.advanceState(); nicholas@2479: }; nicholas@2479: nicholas@2479: function loadTest(page) nicholas@2479: { nicholas@2479: // Called each time a new test page is to be build. The page specification node is the only item passed in nicholas@2479: var content = document.getElementById("timeline-test-content"); nicholas@2479: content.innerHTML = ""; nicholas@2479: var interfaceObj = page.interfaces; nicholas@2479: if (interfaceObj.length > 1) nicholas@2479: { nicholas@2479: console.log("WARNING - This interface only supports one node per page. Using first interface node"); nicholas@2479: } nicholas@2479: interfaceObj = interfaceObj[0]; nicholas@2479: nicholas@2479: //Set the page title nicholas@2479: if (typeof page.title == "string" && page.title.length > 0) nicholas@2479: { nicholas@2479: document.getElementById("test-title").textContent = page.title; nicholas@2479: } nicholas@2479: nicholas@2479: if (interfaceObj.title != null) { nicholas@2479: document.getElementById("page-title").textContent = interfaceObj.title; nicholas@2479: } nicholas@2479: nicholas@2479: // Delete outside reference nicholas@2479: var outsideReferenceHolder = document.getElementById("outside-reference-holder"); nicholas@2479: outsideReferenceHolder.innerHTML = ""; nicholas@2479: nicholas@2479: var commentBoxPrefix = "Comment on track"; nicholas@2479: if (interfaceObj.commentBoxPrefix != undefined) { nicholas@2479: commentBoxPrefix = interfaceObj.commentBoxPrefix; nicholas@2479: } nicholas@2479: nicholas@2479: $(page.audioElements).each(function(index,element){ nicholas@2479: var audioObject = audioEngineContext.newTrack(element); nicholas@2479: if (page.audioElements.type == 'outside-reference') { nicholas@2479: var refNode = interfaceContext.outsideReferenceDOM(audioObject,index,outsideReferenceHolder); nicholas@2479: audioObject.bindInterface(orNode); nicholas@2479: } else { nicholas@2479: switch(audioObject.specification.parent.label) { nicholas@2479: case "none": nicholas@2479: label = ""; nicholas@2479: break; nicholas@2479: case "letter": nicholas@2479: label = String.fromCharCode(97 + index); nicholas@2479: break; nicholas@2479: case "capital": nicholas@2479: label = String.fromCharCode(65 + index); nicholas@2479: break; nicholas@2479: default: nicholas@2479: label = ""+index; nicholas@2479: break; nicholas@2479: } nicholas@2479: var node = new interfaceObject(audioObject,label); nicholas@2479: nicholas@2479: content.appendChild(node.DOM); nicholas@2479: audioObject.bindInterface(node); nicholas@2479: } nicholas@2479: }); nicholas@2479: nicholas@2479: resizeWindow(); nicholas@2479: } nicholas@2479: nicholas@2479: function interfaceObject(audioObject,labelstr) nicholas@2479: { nicholas@2479: // Each audio object has a waveform guide and self-generated comments nicholas@2479: this.parent = audioObject; nicholas@2479: this.DOM = document.createElement("div"); nicholas@2479: this.DOM.className = "timeline-element"; nicholas@2479: this.DOM.id = audioObject.specification.id; nicholas@2479: nicholas@2479: var root = document.createElement("div"); nicholas@2479: root.className = "timeline-element-content"; nicholas@2479: this.DOM.appendChild(root); nicholas@2479: nicholas@2479: var label = document.createElement("div"); nicholas@2479: label.style.textAlign = "center"; nicholas@2479: var labelSpan = document.createElement("span"); nicholas@2479: labelSpan.textContent = "Fragment "+labelstr; nicholas@2479: label.appendChild(labelSpan); nicholas@2479: root.appendChild(label); nicholas@2479: nicholas@2479: var canvasHolder = document.createElement("div"); nicholas@2479: canvasHolder.className = "timeline-element-canvas-holder"; nicholas@2479: var buttonHolder = document.createElement("div"); nicholas@2479: buttonHolder.className = "timeline-element-button-holder"; nicholas@2479: var commentHolder = document.createElement("div"); nicholas@2479: commentHolder.className = "timeline-element-comment-holder"; nicholas@2479: nicholas@2479: root.appendChild(canvasHolder); nicholas@2479: root.appendChild(buttonHolder); nicholas@2479: root.appendChild(commentHolder); nicholas@2479: nicholas@2479: this.comments = { nicholas@2479: parent: this, nicholas@2479: list: [], nicholas@2479: Comment: function(parent,time, str) { nicholas@2479: this.parent = parent; nicholas@2479: this.time = time; nicholas@2479: this.DOM = document.createElement("div"); nicholas@2479: this.DOM.className = "comment-div"; nicholas@2479: this.title = document.createElement("span"); nicholas@2479: if (str != undefined) { nicholas@2479: this.title.textContent = str; nicholas@2479: } else { nicholas@2479: this.title.textContent = "Time: "+time.toFixed(2)+"s"; nicholas@2479: } nicholas@2479: this.textarea = document.createElement("textarea"); nicholas@2479: this.textarea.className = "trackComment"; nicholas@2479: this.DOM.appendChild(this.title); nicholas@2479: this.DOM.appendChild(document.createElement("br")); nicholas@2479: this.DOM.appendChild(this.textarea); nicholas@2479: this.resize = function() { nicholas@2479: var w = window.innerWidth; nicholas@2479: w = Math.min(w,800); nicholas@2479: w = Math.max(w,200); nicholas@2479: var elem_w = w / 2.5; nicholas@2479: elem_w = Math.max(elem_w,190); nicholas@2479: this.DOM.style.width = elem_w+"px"; nicholas@2479: this.textarea.style.width = (elem_w-5)+"px"; nicholas@2479: } nicholas@2479: this.resize(); nicholas@2479: }, nicholas@2479: newComment: function(time) { nicholas@2479: var node = new this.Comment(this,time); nicholas@2479: this.list.push(node); nicholas@2479: commentHolder.appendChild(node.DOM); nicholas@2479: return node; nicholas@2479: }, nicholas@2479: deleteComment: function(comment) { nicholas@2479: nicholas@2479: }, nicholas@2479: clearList: function() { nicholas@2479: nicholas@2479: } nicholas@2479: } nicholas@2479: nicholas@2479: this.canvas = { nicholas@2479: parent: this, nicholas@2479: comments: this.comments, nicholas@2479: layer1: document.createElement("canvas"), nicholas@2479: layer2: document.createElement("canvas"), nicholas@2479: layer3: document.createElement("canvas"), nicholas@2479: layer4: document.createElement("canvas"), nicholas@2479: resize: function(w) { nicholas@2479: this.layer1.width = w; nicholas@2479: this.layer2.width = w; nicholas@2479: this.layer3.width = w; nicholas@2479: this.layer4.width = w; nicholas@2479: this.layer1.style.width = w+"px"; nicholas@2479: this.layer2.style.width = w+"px"; nicholas@2479: this.layer3.style.width = w+"px"; nicholas@2479: this.layer4.style.width = w+"px"; nicholas@2479: }, nicholas@2479: handleEvent: function(event) { nicholas@2479: switch(event.currentTarget) { nicholas@2479: case this.layer1: nicholas@2479: switch(event.type) { nicholas@2479: case "mousemove": nicholas@2479: this.drawMouse(event); nicholas@2479: break; nicholas@2479: case "mouseleave": nicholas@2479: this.clearCanvas(this.layer1); nicholas@2479: break; nicholas@2479: case "click": nicholas@2479: var rect = this.layer1.getBoundingClientRect(); nicholas@2479: var pixX = event.clientX - rect.left; nicholas@2479: var tpp = this.parent.parent.buffer.buffer.duration/this.layer1.width; nicholas@2479: this.comments.newComment(pixX*tpp); nicholas@2479: this.drawMarkers(); nicholas@2479: break; nicholas@2479: } nicholas@2479: break; nicholas@2479: } nicholas@2479: }, nicholas@2479: drawWaveform: function() { nicholas@2479: var buffer = this.parent.parent.buffer.buffer; nicholas@2479: var context = this.layer4.getContext("2d"); nicholas@2479: context.lineWidth = 1; nicholas@2479: context.strokeStyle = "#888"; nicholas@2479: context.clearRect(0,0,this.layer4.width, this.layer4.height); nicholas@2479: var data = buffer.getChannelData(0); nicholas@2479: var t_per_pixel = buffer.duration/this.layer4.width; nicholas@2479: var s_per_pixel = data.length/this.layer4.width; nicholas@2479: var pixX = 0; nicholas@2479: while (pixX < this.layer4.width) { nicholas@2479: var start = Math.floor(s_per_pixel*pixX); nicholas@2479: var end = Math.min(Math.ceil(s_per_pixel*(pixX+1)),data.length); nicholas@2479: var frame = data.subarray(start,end); nicholas@2479: var min = frame[0]; nicholas@2479: var max = min; nicholas@2479: for (var n=0; n max) {max = frame[n];} nicholas@2479: } nicholas@2479: // Assuming min/max normalised between [-1, 1] to map to [150, 0] nicholas@2479: context.beginPath(); nicholas@2479: context.moveTo(pixX+0.5,(min+1)*-75+150); nicholas@2479: context.lineTo(pixX+0.5,(max+1)*-75+150); nicholas@2479: context.stroke(); nicholas@2479: pixX++; nicholas@2479: } nicholas@2479: }, nicholas@2479: drawMouse: function(event) { nicholas@2479: var context = this.layer1.getContext("2d"); nicholas@2479: context.clearRect(0,0,this.layer1.width, this.layer1.height); nicholas@2479: var rect = this.layer1.getBoundingClientRect(); nicholas@2479: var pixX = event.clientX - rect.left; nicholas@2479: pixX = Math.floor(pixX)-0.5; nicholas@2479: context.strokeStyle = "#800"; nicholas@2479: context.beginPath(); nicholas@2479: context.moveTo(pixX,0); nicholas@2479: context.lineTo(pixX,this.layer1.height); nicholas@2479: context.stroke(); nicholas@2479: }, nicholas@2479: drawTicker: function() { nicholas@2479: var context = this.layer2.getContext("2d"); nicholas@2479: context.clearRect(0,0,this.layer2.width, this.layer2.height); nicholas@2479: var time = this.parent.parent.getCurrentPosition(); nicholas@2479: var ratio = time / this.parent.parent.buffer.buffer.duration; nicholas@2479: var pixX = Math.floor(ratio*this.layer2.width)+0.5; nicholas@2479: context.strokeStyle = "#080"; nicholas@2479: context.beginPath(); nicholas@2479: context.moveTo(pixX,0); nicholas@2479: context.lineTo(pixX,this.layer2.height); nicholas@2479: context.stroke(); nicholas@2479: }, nicholas@2479: drawMarkers: function() { nicholas@2479: var context = this.layer3.getContext("2d"); nicholas@2479: context.clearRect(0,0,this.layer3.width, this.layer3.height); nicholas@2479: context.strokeStyle = "#008"; nicholas@2479: var tpp = this.parent.parent.buffer.buffer.duration/this.layer1.width; nicholas@2479: for (var i=0; i node. nicholas@2479: // If there is no value node (such as outside reference), return null nicholas@2479: // If there are multiple value nodes (such as multiple scale / 2D scales), return an array of nodes with each value node having an 'interfaceName' attribute nicholas@2479: // Use storage.document.createElement('value'); to generate the XML node. nicholas@2479: return null; nicholas@2479: }; nicholas@2479: this.error = function() { nicholas@2479: // If there is an error with the audioObject, this will be called to indicate a failure nicholas@2479: } nicholas@2479: }; nicholas@2479: nicholas@2479: function resizeWindow(event) nicholas@2479: { nicholas@2479: // Called on every window resize event, use this to scale your page properly nicholas@2479: for (var i=0; i saves nicholas@2479: // Get the current information in store (remember to appendChild your data to it) nicholas@2479: // pageSpecification is the current page node configuration nicholas@2479: // To create new XML nodes, use storage.document.createElement(); nicholas@2479: }