Mercurial > hg > webaudioevaluationtool
view interfaces/timeline.js @ 2479:dbe43b4ab7aa
Starting timeline.js interface
author | Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk> |
---|---|
date | Wed, 03 Aug 2016 14:52:04 +0100 |
parents | |
children | 713a2d059a16 |
line wrap: on
line source
/** * WAET Timeline * This interface plots a waveform timeline per audio fragment on a page. Clicking on the fragment will generate a comment box for processing. */ // Once this is loaded and parsed, begin execution loadInterface(); function loadInterface() { // Use this to do any one-time page / element construction. For instance, placing any stationary text objects, // holding div's, or setting up any nodes which are present for the entire test sequence interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema interfaceContext.insertPoint = document.getElementById("topLevelBody"); var testContent = document.createElement("div"); // Create the top div and Title element var title = document.createElement("div"); title.className = "title"; title.align = "center"; var titleSpan = document.createElement("span"); titleSpan.id = "test-title"; titleSpan.textContent = "Listening Test"; title.appendChild(titleSpan); var pagetitle = document.createElement("div"); pagetitle.className = "pageTitle"; pagetitle.align = "center"; titleSpan = document.createElement("span"); titleSpan.id = "page-title"; pagetitle.appendChild(titleSpan); // Create Interface buttons var interfaceButtons = document.createElement("div"); interfaceButtons.id = 'interface-buttons'; interfaceButtons.style.height = "25px"; // Create playback start/stop points var playback = document.createElement("button"); playback.innerHTML = "Stop"; playback.id = "playback-button"; playback.onclick = function() { if (audioEngineContext.status == 1) { audioEngineContext.stop(); this.innerHTML = "Stop"; var time = audioEngineContext.timer.getTestTime(); console.log("Stopped at "+time); } }; // Create Submit (save) button var submit = document.createElement("button"); submit.innerHTML = 'Next'; submit.onclick = buttonSubmitClick; submit.id = 'submit-button'; submit.style.float = 'left'; // Append the interface buttons into the interfaceButtons object. interfaceButtons.appendChild(playback); interfaceButtons.appendChild(submit); // Create outside reference holder var outsideRef = document.createElement("div"); outsideRef.id = "outside-reference-holder"; // Create content point var content = document.createElement("div"); content.id = "timeline-test-content"; //Inject testContent.appendChild(title); testContent.appendChild(pagetitle); testContent.appendChild(interfaceButtons); testContent.appendChild(outsideRef); testContent.appendChild(content); interfaceContext.insertPoint.appendChild(testContent); // Load the full interface testState.initialise(); testState.advanceState(); }; function loadTest(page) { // Called each time a new test page is to be build. The page specification node is the only item passed in var content = document.getElementById("timeline-test-content"); content.innerHTML = ""; var interfaceObj = page.interfaces; if (interfaceObj.length > 1) { console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node"); } interfaceObj = interfaceObj[0]; //Set the page title if (typeof page.title == "string" && page.title.length > 0) { document.getElementById("test-title").textContent = page.title; } if (interfaceObj.title != null) { document.getElementById("page-title").textContent = interfaceObj.title; } // Delete outside reference var outsideReferenceHolder = document.getElementById("outside-reference-holder"); outsideReferenceHolder.innerHTML = ""; var commentBoxPrefix = "Comment on track"; if (interfaceObj.commentBoxPrefix != undefined) { commentBoxPrefix = interfaceObj.commentBoxPrefix; } $(page.audioElements).each(function(index,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 node = new interfaceObject(audioObject,label); content.appendChild(node.DOM); audioObject.bindInterface(node); } }); resizeWindow(); } function interfaceObject(audioObject,labelstr) { // Each audio object has a waveform guide and self-generated comments this.parent = audioObject; this.DOM = document.createElement("div"); this.DOM.className = "timeline-element"; this.DOM.id = audioObject.specification.id; var root = document.createElement("div"); root.className = "timeline-element-content"; this.DOM.appendChild(root); var label = document.createElement("div"); label.style.textAlign = "center"; var labelSpan = document.createElement("span"); labelSpan.textContent = "Fragment "+labelstr; label.appendChild(labelSpan); root.appendChild(label); var canvasHolder = document.createElement("div"); canvasHolder.className = "timeline-element-canvas-holder"; var buttonHolder = document.createElement("div"); buttonHolder.className = "timeline-element-button-holder"; var commentHolder = document.createElement("div"); commentHolder.className = "timeline-element-comment-holder"; root.appendChild(canvasHolder); root.appendChild(buttonHolder); root.appendChild(commentHolder); this.comments = { parent: this, list: [], Comment: function(parent,time, str) { this.parent = parent; this.time = time; this.DOM = document.createElement("div"); this.DOM.className = "comment-div"; this.title = document.createElement("span"); if (str != undefined) { this.title.textContent = str; } else { this.title.textContent = "Time: "+time.toFixed(2)+"s"; } this.textarea = document.createElement("textarea"); this.textarea.className = "trackComment"; this.DOM.appendChild(this.title); this.DOM.appendChild(document.createElement("br")); this.DOM.appendChild(this.textarea); this.resize = function() { var w = window.innerWidth; w = Math.min(w,800); w = Math.max(w,200); var elem_w = w / 2.5; elem_w = Math.max(elem_w,190); this.DOM.style.width = elem_w+"px"; this.textarea.style.width = (elem_w-5)+"px"; } this.resize(); }, newComment: function(time) { var node = new this.Comment(this,time); this.list.push(node); commentHolder.appendChild(node.DOM); return node; }, deleteComment: function(comment) { }, clearList: function() { } } this.canvas = { parent: this, comments: this.comments, layer1: document.createElement("canvas"), layer2: document.createElement("canvas"), layer3: document.createElement("canvas"), layer4: document.createElement("canvas"), resize: function(w) { this.layer1.width = w; this.layer2.width = w; this.layer3.width = w; this.layer4.width = w; this.layer1.style.width = w+"px"; this.layer2.style.width = w+"px"; this.layer3.style.width = w+"px"; this.layer4.style.width = w+"px"; }, handleEvent: function(event) { switch(event.currentTarget) { case this.layer1: switch(event.type) { case "mousemove": this.drawMouse(event); break; case "mouseleave": this.clearCanvas(this.layer1); break; case "click": var rect = this.layer1.getBoundingClientRect(); var pixX = event.clientX - rect.left; var tpp = this.parent.parent.buffer.buffer.duration/this.layer1.width; this.comments.newComment(pixX*tpp); this.drawMarkers(); break; } break; } }, drawWaveform: function() { var buffer = this.parent.parent.buffer.buffer; var context = this.layer4.getContext("2d"); context.lineWidth = 1; context.strokeStyle = "#888"; context.clearRect(0,0,this.layer4.width, this.layer4.height); var data = buffer.getChannelData(0); var t_per_pixel = buffer.duration/this.layer4.width; var s_per_pixel = data.length/this.layer4.width; var pixX = 0; while (pixX < this.layer4.width) { var start = Math.floor(s_per_pixel*pixX); var end = Math.min(Math.ceil(s_per_pixel*(pixX+1)),data.length); var frame = data.subarray(start,end); var min = frame[0]; var max = min; for (var n=0; n<frame.length; n++) { if (frame[n] < min) {min = frame[n];} if (frame[n] > max) {max = frame[n];} } // Assuming min/max normalised between [-1, 1] to map to [150, 0] context.beginPath(); context.moveTo(pixX+0.5,(min+1)*-75+150); context.lineTo(pixX+0.5,(max+1)*-75+150); context.stroke(); pixX++; } }, drawMouse: function(event) { var context = this.layer1.getContext("2d"); context.clearRect(0,0,this.layer1.width, this.layer1.height); var rect = this.layer1.getBoundingClientRect(); var pixX = event.clientX - rect.left; pixX = Math.floor(pixX)-0.5; context.strokeStyle = "#800"; context.beginPath(); context.moveTo(pixX,0); context.lineTo(pixX,this.layer1.height); context.stroke(); }, drawTicker: function() { var context = this.layer2.getContext("2d"); context.clearRect(0,0,this.layer2.width, this.layer2.height); var time = this.parent.parent.getCurrentPosition(); var ratio = time / this.parent.parent.buffer.buffer.duration; var pixX = Math.floor(ratio*this.layer2.width)+0.5; context.strokeStyle = "#080"; context.beginPath(); context.moveTo(pixX,0); context.lineTo(pixX,this.layer2.height); context.stroke(); }, drawMarkers: function() { var context = this.layer3.getContext("2d"); context.clearRect(0,0,this.layer3.width, this.layer3.height); context.strokeStyle = "#008"; var tpp = this.parent.parent.buffer.buffer.duration/this.layer1.width; for (var i=0; i<this.comments.list.length; i++) { var comment = this.comments.list[i]; var pixX = Math.floor(comment.time/tpp)+0.5; context.beginPath(); context.moveTo(pixX,0); context.lineTo(pixX,this.layer3.height); context.stroke(); } }, clearCanvas: function(canvas) { var context = canvas.getContext("2d"); context.clearRect(0,0,canvas.width, canvas.height); } } this.canvas.layer1.className = "timeline-element-canvas canvas-layer1 canvas-disabled"; this.canvas.layer2.className = "timeline-element-canvas canvas-layer2"; this.canvas.layer3.className = "timeline-element-canvas canvas-layer3"; this.canvas.layer4.className = "timeline-element-canvas canvas-layer3"; this.canvas.layer1.height = "150"; this.canvas.layer2.height = "150"; this.canvas.layer3.height = "150"; this.canvas.layer4.height = "150"; canvasHolder.appendChild(this.canvas.layer1); canvasHolder.appendChild(this.canvas.layer2); canvasHolder.appendChild(this.canvas.layer3); canvasHolder.appendChild(this.canvas.layer4); this.canvas.layer1.addEventListener("mousemove",this.canvas); this.canvas.layer1.addEventListener("mouseleave",this.canvas); this.canvas.layer1.addEventListener("click",this.canvas); var canvasIntervalID = null; this.playButton = { parent: this, DOM: document.createElement("button"), handleEvent: function(event) { var id = this.parent.parent.id; var str = this.DOM.textContent; if (str == "Play") { audioEngineContext.play(id); } else if (str == "Stop") { audioEngineContext.stop(); } } } this.playButton.DOM.addEventListener("click",this.playButton); this.playButton.DOM.className = "timeline-button timeline-button-disabled"; this.playButton.DOM.disabled = true; this.playButton.DOM.textContent = "Wait"; this.clearButton = { parent: this, DOM: document.createElement("button"), handleEvent: function(event) { this.parent.comments.clearList(); } } this.clearButton.DOM.addEventListener("click",this.clearButton); this.clearButton.DOM.className = "timeline-button"; this.clearButton.DOM.textContent = "Clear"; buttonHolder.appendChild(this.playButton.DOM); buttonHolder.appendChild(this.clearButton.DOM); this.resize = function() { var w = window.innerWidth; w = Math.min(w,800); w = Math.max(w,200); root.style.width = w+"px"; var c_w = w-100; this.canvas.resize(c_w); } this.enable = function() { // This is used to tell the interface object that playback of this node is ready this.canvas.layer1.addEventListener("click",this.canvas); this.canvas.layer1.className = "timeline-element-canvas canvas-layer1"; this.playButton.DOM.className = "timeline-button timeline-button-play"; this.playButton.DOM.textContent = "Play"; this.playButton.DOM.disabled = false; this.canvas.drawWaveform(); }; this.updateLoading = function(progress) { // progress is a value from 0 to 100 indicating the current download state of media files progress = String(progress); progress = progress.substr(0,5); this.playButton.DOM.textContent = "Loading: "+progress+'%'; }; this.startPlayback = function() { // Called when playback has begun canvasIntervalID = window.setInterval(this.canvas.drawTicker.bind(this.canvas),100); this.playButton.DOM.textContent = "Stop"; }; this.stopPlayback = function() { // Called when playback has stopped. This gets called even if playback never started! window.clearInterval(canvasIntervalID); this.canvas.clearCanvas(this.canvas.layer2); this.playButton.DOM.textContent = "Play"; }; this.getValue = function() { // Return the current value of the object. If there is no value, return 0 return 0; }; this.getPresentedId = function() { // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale return labelSpan.textContent; }; this.canMove = function() { // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale. // These are checked primarily if the interface check option 'fragmentMoved' is enabled. return false; }; this.exportXMLDOM = function(audioObject) { // Called by the audioObject holding this element to export the interface <value> node. // If there is no value node (such as outside reference), return null // If there are multiple value nodes (such as multiple scale / 2D scales), return an array of nodes with each value node having an 'interfaceName' attribute // Use storage.document.createElement('value'); to generate the XML node. return null; }; this.error = function() { // If there is an error with the audioObject, this will be called to indicate a failure } }; function resizeWindow(event) { // Called on every window resize event, use this to scale your page properly for (var i=0; i<audioEngineContext.audioObjects.length; i++) { audioEngineContext.audioObjects[i].interfaceDOM.resize(); } } function buttonSubmitClick() { } 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(); }