annotate interfaces/timeline.js @ 3141:335bc77627e0 tip

fixing discrete interface to allow labels to display
author Dave Moffat <me@davemoffat.com>
date Mon, 26 Jul 2021 12:15:24 +0100
parents 1620cbee9111
children
rev   line source
nicholas@2479 1 /**
nicholas@2479 2 * WAET Timeline
nicholas@2479 3 * 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 4 */
nicholas@2702 5 /*globals interfaceContext, window, document, console, audioEngineContext, testState, $, storage */
nicholas@2479 6 // Once this is loaded and parsed, begin execution
nicholas@2479 7 loadInterface();
nicholas@2479 8
nicholas@2479 9 function loadInterface() {
nicholas@2481 10 // Use this to do any one-time page / element construction. For instance, placing any stationary text objects,
nicholas@2481 11 // holding div's, or setting up any nodes which are present for the entire test sequence
nicholas@2481 12
nicholas@2479 13 interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema
nicholas@2481 14
nicholas@2479 15 interfaceContext.insertPoint = document.getElementById("topLevelBody");
nicholas@2479 16 var testContent = document.createElement("div");
nicholas@2481 17
nicholas@2479 18 // Create the top div and Title element
nicholas@2479 19 var title = document.createElement("div");
nicholas@2479 20 title.className = "title";
nicholas@2479 21 title.align = "center";
nicholas@2479 22 var titleSpan = document.createElement("span");
nicholas@2479 23 titleSpan.id = "test-title";
nicholas@2479 24 titleSpan.textContent = "Listening Test";
nicholas@2479 25 title.appendChild(titleSpan);
nicholas@2481 26
nicholas@2479 27 var pagetitle = document.createElement("div");
nicholas@2479 28 pagetitle.className = "pageTitle";
nicholas@2479 29 pagetitle.align = "center";
nicholas@2479 30 titleSpan = document.createElement("span");
nicholas@2479 31 titleSpan.id = "page-title";
nicholas@2479 32 pagetitle.appendChild(titleSpan);
nicholas@2481 33
nicholas@2479 34 // Create Interface buttons
nicholas@2479 35 var interfaceButtons = document.createElement("div");
nicholas@2479 36 interfaceButtons.id = 'interface-buttons';
nicholas@2479 37 interfaceButtons.style.height = "25px";
nicholas@2481 38
nicholas@2479 39 // Create playback start/stop points
nicholas@2479 40 var playback = document.createElement("button");
nicholas@2479 41 playback.innerHTML = "Stop";
nicholas@2479 42 playback.id = "playback-button";
nicholas@2481 43 playback.onclick = function () {
nicholas@2479 44 if (audioEngineContext.status == 1) {
nicholas@2479 45 audioEngineContext.stop();
nicholas@2479 46 this.innerHTML = "Stop";
nicholas@2479 47 var time = audioEngineContext.timer.getTestTime();
nicholas@2481 48 console.log("Stopped at " + time);
nicholas@2479 49 }
nicholas@2479 50 };
nicholas@2479 51 // Create Submit (save) button
nicholas@2481 52 var submit = document.createElement("button");
nicholas@2481 53 submit.innerHTML = 'Next';
nicholas@2481 54 submit.onclick = buttonSubmitClick;
nicholas@2481 55 submit.id = 'submit-button';
nicholas@2481 56 submit.style.float = 'left';
nicholas@2481 57 // Append the interface buttons into the interfaceButtons object.
nicholas@2557 58 interfaceButtons.appendChild(submit);
nicholas@2481 59 interfaceButtons.appendChild(playback);
nicholas@2481 60
nicholas@2479 61 // Create outside reference holder
nicholas@2479 62 var outsideRef = document.createElement("div");
nicholas@2479 63 outsideRef.id = "outside-reference-holder";
nicholas@2481 64
nicholas@2479 65 // Create content point
nicholas@2479 66 var content = document.createElement("div");
nicholas@2479 67 content.id = "timeline-test-content";
nicholas@2481 68
nicholas@2479 69 //Inject
nicholas@2479 70 testContent.appendChild(title);
nicholas@2479 71 testContent.appendChild(pagetitle);
nicholas@2479 72 testContent.appendChild(interfaceButtons);
nicholas@2479 73 testContent.appendChild(outsideRef);
nicholas@2479 74 testContent.appendChild(content);
nicholas@2479 75 interfaceContext.insertPoint.appendChild(testContent);
nicholas@2481 76
nicholas@2479 77 // Load the full interface
nicholas@2481 78 testState.initialise();
nicholas@2481 79 testState.advanceState();
nicholas@2702 80 }
nicholas@2479 81
nicholas@2481 82 function loadTest(page) {
nicholas@2481 83 // Called each time a new test page is to be build. The page specification node is the only item passed in
nicholas@2479 84 var content = document.getElementById("timeline-test-content");
nicholas@2479 85 content.innerHTML = "";
nicholas@2651 86 var interfaceObj = interfaceContext.getCombinedInterfaces(page);
nicholas@2481 87 if (interfaceObj.length > 1) {
nicholas@2479 88 console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
nicholas@2479 89 }
nicholas@2479 90 interfaceObj = interfaceObj[0];
nicholas@2481 91
nicholas@2479 92 //Set the page title
nicholas@2481 93 if (typeof page.title == "string" && page.title.length > 0) {
nicholas@2479 94 document.getElementById("test-title").textContent = page.title;
nicholas@2479 95 }
nicholas@2481 96
nicholas@2702 97 if (interfaceObj.title !== null) {
nicholas@2479 98 document.getElementById("page-title").textContent = interfaceObj.title;
nicholas@2479 99 }
nicholas@2481 100
nicholas@2809 101 if (interfaceObj.image !== undefined) {
nicholas@2807 102 document.getElementById("timeline-test-content").parentElement.insertBefore(interfaceContext.imageHolder.root, document.getElementById("timeline-test-content"));
nicholas@2801 103 interfaceContext.imageHolder.setImage(interfaceObj.image);
nicholas@2801 104 }
nicholas@2801 105
nicholas@2479 106 // Delete outside reference
nicholas@2481 107 var outsideReferenceHolder = document.getElementById("outside-reference-holder");
nicholas@2479 108 outsideReferenceHolder.innerHTML = "";
nicholas@2481 109
nicholas@2479 110 var commentBoxPrefix = "Comment on track";
nicholas@2702 111 if (interfaceObj.commentBoxPrefix !== undefined) {
nicholas@2481 112 commentBoxPrefix = interfaceObj.commentBoxPrefix;
nicholas@2481 113 }
nicholas@2607 114 var index = 0;
nicholas@2607 115 var interfaceScales = testState.currentStateMap.interfaces[0].scales;
nicholas@2607 116 var labelType = page.label;
nicholas@2607 117 if (labelType == "default") {
nicholas@2607 118 labelType = "number";
nicholas@2607 119 }
nicholas@2607 120 $(page.audioElements).each(function (pageIndex, element) {
nicholas@2479 121 var audioObject = audioEngineContext.newTrack(element);
nicholas@2479 122 if (page.audioElements.type == 'outside-reference') {
nicholas@2481 123 var refNode = interfaceContext.outsideReferenceDOM(audioObject, index, outsideReferenceHolder);
nicholas@2702 124 audioObject.bindInterface(refNode);
nicholas@2479 125 } else {
nicholas@2607 126 var label = interfaceContext.getLabel(labelType, index, page.labelStart);
nicholas@2481 127 var node = new interfaceObject(audioObject, label);
nicholas@2481 128
nicholas@2479 129 content.appendChild(node.DOM);
nicholas@2479 130 audioObject.bindInterface(node);
nicholas@2479 131 }
nicholas@2479 132 });
nicholas@2481 133
nicholas@2479 134 resizeWindow();
nicholas@2479 135 }
nicholas@2479 136
nicholas@2481 137 function interfaceObject(audioObject, labelstr) {
nicholas@2481 138 // Each audio object has a waveform guide and self-generated comments
nicholas@2479 139 this.parent = audioObject;
nicholas@2479 140 this.DOM = document.createElement("div");
nicholas@2479 141 this.DOM.className = "timeline-element";
nicholas@2479 142 this.DOM.id = audioObject.specification.id;
nicholas@2481 143
nicholas@2479 144 var root = document.createElement("div");
nicholas@2479 145 root.className = "timeline-element-content";
nicholas@2479 146 this.DOM.appendChild(root);
nicholas@2481 147
nicholas@2479 148 var label = document.createElement("div");
nicholas@2479 149 label.style.textAlign = "center";
nicholas@2479 150 var labelSpan = document.createElement("span");
nicholas@2481 151 labelSpan.textContent = "Fragment " + labelstr;
nicholas@2479 152 label.appendChild(labelSpan);
nicholas@2479 153 root.appendChild(label);
nicholas@2481 154
nicholas@2479 155 var canvasHolder = document.createElement("div");
nicholas@2479 156 canvasHolder.className = "timeline-element-canvas-holder";
nicholas@2479 157 var buttonHolder = document.createElement("div");
nicholas@2479 158 buttonHolder.className = "timeline-element-button-holder";
nicholas@2479 159 var commentHolder = document.createElement("div");
nicholas@2479 160 commentHolder.className = "timeline-element-comment-holder";
nicholas@2481 161
nicholas@2479 162 root.appendChild(canvasHolder);
nicholas@2479 163 root.appendChild(buttonHolder);
nicholas@2479 164 root.appendChild(commentHolder);
nicholas@2481 165
nicholas@2479 166 this.comments = {
nicholas@2479 167 parent: this,
nicholas@2479 168 list: [],
nicholas@2481 169 Comment: function (parent, time, str) {
nicholas@2479 170 this.parent = parent;
nicholas@2479 171 this.time = time;
nicholas@2479 172 this.DOM = document.createElement("div");
nicholas@2480 173 this.DOM.className = "comment-entry";
nicholas@2480 174 var titleHolder = document.createElement("div");
nicholas@2480 175 titleHolder.className = "comment-entry-header";
nicholas@2479 176 this.title = document.createElement("span");
nicholas@2702 177 if (str !== undefined) {
nicholas@2479 178 this.title.textContent = str;
nicholas@2479 179 } else {
nicholas@2481 180 this.title.textContent = "Time: " + time.toFixed(2) + "s";
nicholas@2479 181 }
nicholas@2480 182 titleHolder.appendChild(this.title);
nicholas@2479 183 this.textarea = document.createElement("textarea");
nicholas@2480 184 this.textarea.className = "comment-entry-text";
nicholas@2480 185 this.DOM.appendChild(titleHolder);
nicholas@2479 186 this.DOM.appendChild(this.textarea);
nicholas@2481 187
nicholas@2480 188 this.clear = {
nicholas@2480 189 DOM: document.createElement("button"),
nicholas@2480 190 parent: this,
nicholas@2481 191 handleEvent: function () {
nicholas@2480 192 this.parent.parent.deleteComment(this.parent);
nicholas@2480 193 }
nicholas@2702 194 };
nicholas@2480 195 this.clear.DOM.textContent = "Delete";
nicholas@2481 196 this.clear.DOM.addEventListener("click", this.clear);
nicholas@2480 197 titleHolder.appendChild(this.clear.DOM);
nicholas@2481 198
nicholas@2481 199 this.resize = function () {
nicholas@2479 200 var w = window.innerWidth;
nicholas@2481 201 w = Math.min(w, 800);
nicholas@2481 202 w = Math.max(w, 200);
nicholas@2479 203 var elem_w = w / 2.5;
nicholas@2481 204 elem_w = Math.max(elem_w, 190);
nicholas@2481 205 this.DOM.style.width = elem_w + "px";
nicholas@2481 206 this.textarea.style.width = (elem_w - 5) + "px";
nicholas@2702 207 };
nicholas@2481 208 this.buildXML = function (root) {
nicholas@2480 209 //storage.document.createElement();
nicholas@2480 210 var node = storage.document.createElement("comment");
nicholas@2480 211 var question = storage.document.createElement("question");
nicholas@2480 212 var comment = storage.document.createElement("response");
nicholas@2481 213 node.setAttribute("time", this.time);
nicholas@2480 214 question.textContent = this.title.textContent;
nicholas@2480 215 comment.textContent = this.textarea.value;
nicholas@2480 216 node.appendChild(question);
nicholas@2480 217 node.appendChild(comment);
nicholas@2480 218 root.appendChild(node);
nicholas@2702 219 };
nicholas@2479 220 this.resize();
nicholas@2479 221 },
nicholas@2481 222 newComment: function (time) {
nicholas@2481 223 var node = new this.Comment(this, time);
nicholas@2479 224 this.list.push(node);
nicholas@2479 225 commentHolder.appendChild(node.DOM);
nicholas@2479 226 return node;
nicholas@2479 227 },
nicholas@2481 228 deleteComment: function (comment) {
nicholas@2481 229 var index = this.list.findIndex(function (element, index, array) {
nicholas@2481 230 if (element == comment) {
nicholas@2481 231 return true;
nicholas@2481 232 }
nicholas@2481 233 return false;
nicholas@2481 234 }, comment);
nicholas@2480 235 if (index == -1) {
nicholas@2480 236 return false;
nicholas@2480 237 }
nicholas@2481 238 var node = this.list.splice(index, 1);
nicholas@2480 239 comment.DOM.remove();
nicholas@2480 240 this.parent.canvas.drawMarkers();
nicholas@2480 241 return true;
nicholas@2479 242 },
nicholas@2481 243 clearList: function () {
nicholas@2481 244 while (this.list.length > 0) {
nicholas@2480 245 this.deleteComment(this.list[0]);
nicholas@2480 246 }
nicholas@2479 247 }
nicholas@2702 248 };
nicholas@2481 249
nicholas@2479 250 this.canvas = {
nicholas@2479 251 parent: this,
nicholas@2479 252 comments: this.comments,
nicholas@2479 253 layer1: document.createElement("canvas"),
nicholas@2479 254 layer2: document.createElement("canvas"),
nicholas@2479 255 layer3: document.createElement("canvas"),
nicholas@2479 256 layer4: document.createElement("canvas"),
nicholas@2808 257 resize: function () {
nicholas@2808 258 var w = $(this.layer1.parentElement).width();
nicholas@2479 259 this.layer1.width = w;
nicholas@2479 260 this.layer2.width = w;
nicholas@2479 261 this.layer3.width = w;
nicholas@2479 262 this.layer4.width = w;
nicholas@2481 263 this.layer1.style.width = w + "px";
nicholas@2481 264 this.layer2.style.width = w + "px";
nicholas@2481 265 this.layer3.style.width = w + "px";
nicholas@2481 266 this.layer4.style.width = w + "px";
nicholas@2480 267 this.drawWaveform();
nicholas@2480 268 this.drawMarkers();
nicholas@2479 269 },
nicholas@2481 270 handleEvent: function (event) {
nicholas@2481 271 switch (event.currentTarget) {
nicholas@2479 272 case this.layer1:
nicholas@2481 273 switch (event.type) {
nicholas@2479 274 case "mousemove":
nicholas@2479 275 this.drawMouse(event);
nicholas@2479 276 break;
nicholas@2479 277 case "mouseleave":
nicholas@2479 278 this.clearCanvas(this.layer1);
nicholas@2479 279 break;
nicholas@2479 280 case "click":
nicholas@2479 281 var rect = this.layer1.getBoundingClientRect();
nicholas@2479 282 var pixX = event.clientX - rect.left;
nicholas@2481 283 var tpp = this.parent.parent.buffer.buffer.duration / this.layer1.width;
nicholas@2481 284 this.comments.newComment(pixX * tpp);
nicholas@2479 285 this.drawMarkers();
nicholas@2479 286 break;
nicholas@2479 287 }
nicholas@2479 288 break;
nicholas@2479 289 }
nicholas@2479 290 },
nicholas@2481 291 drawWaveform: function () {
nicholas@2702 292 if (this.parent.parent === undefined || this.parent.parent.buffer === undefined) {
nicholas@2480 293 return;
nicholas@2480 294 }
nicholas@2479 295 var buffer = this.parent.parent.buffer.buffer;
nicholas@2479 296 var context = this.layer4.getContext("2d");
nicholas@2479 297 context.lineWidth = 1;
nicholas@2479 298 context.strokeStyle = "#888";
nicholas@2481 299 context.clearRect(0, 0, this.layer4.width, this.layer4.height);
nicholas@2479 300 var data = buffer.getChannelData(0);
nicholas@2481 301 var t_per_pixel = buffer.duration / this.layer4.width;
nicholas@2481 302 var s_per_pixel = data.length / this.layer4.width;
nicholas@2479 303 var pixX = 0;
nicholas@2479 304 while (pixX < this.layer4.width) {
nicholas@2481 305 var start = Math.floor(s_per_pixel * pixX);
nicholas@2481 306 var end = Math.min(Math.ceil(s_per_pixel * (pixX + 1)), data.length);
nicholas@2481 307 var frame = data.subarray(start, end);
nicholas@2479 308 var min = frame[0];
nicholas@2479 309 var max = min;
nicholas@2481 310 for (var n = 0; n < frame.length; n++) {
nicholas@2481 311 if (frame[n] < min) {
nicholas@2481 312 min = frame[n];
nicholas@2481 313 }
nicholas@2481 314 if (frame[n] > max) {
nicholas@2481 315 max = frame[n];
nicholas@2481 316 }
nicholas@2479 317 }
nicholas@2479 318 // Assuming min/max normalised between [-1, 1] to map to [150, 0]
nicholas@2479 319 context.beginPath();
nicholas@2481 320 context.moveTo(pixX + 0.5, (min + 1) * -75 + 150);
nicholas@2481 321 context.lineTo(pixX + 0.5, (max + 1) * -75 + 150);
nicholas@2479 322 context.stroke();
nicholas@2479 323 pixX++;
nicholas@2479 324 }
nicholas@2479 325 },
nicholas@2481 326 drawMouse: function (event) {
nicholas@2479 327 var context = this.layer1.getContext("2d");
nicholas@2481 328 context.clearRect(0, 0, this.layer1.width, this.layer1.height);
nicholas@2479 329 var rect = this.layer1.getBoundingClientRect();
nicholas@2479 330 var pixX = event.clientX - rect.left;
nicholas@2481 331 pixX = Math.floor(pixX) - 0.5;
nicholas@2479 332 context.strokeStyle = "#800";
nicholas@2479 333 context.beginPath();
nicholas@2481 334 context.moveTo(pixX, 0);
nicholas@2481 335 context.lineTo(pixX, this.layer1.height);
nicholas@2479 336 context.stroke();
nicholas@2479 337 },
nicholas@2481 338 drawTicker: function () {
nicholas@2479 339 var context = this.layer2.getContext("2d");
nicholas@2481 340 context.clearRect(0, 0, this.layer2.width, this.layer2.height);
nicholas@2479 341 var time = this.parent.parent.getCurrentPosition();
nicholas@2479 342 var ratio = time / this.parent.parent.buffer.buffer.duration;
nicholas@2481 343 var pixX = Math.floor(ratio * this.layer2.width) + 0.5;
nicholas@2479 344 context.strokeStyle = "#080";
nicholas@2479 345 context.beginPath();
nicholas@2481 346 context.moveTo(pixX, 0);
nicholas@2481 347 context.lineTo(pixX, this.layer2.height);
nicholas@2479 348 context.stroke();
nicholas@2479 349 },
nicholas@2481 350 drawMarkers: function () {
nicholas@2702 351 if (this.parent.parent === undefined || this.parent.parent.buffer === undefined) {
nicholas@2480 352 return;
nicholas@2480 353 }
nicholas@2479 354 var context = this.layer3.getContext("2d");
nicholas@2481 355 context.clearRect(0, 0, this.layer3.width, this.layer3.height);
nicholas@2479 356 context.strokeStyle = "#008";
nicholas@2481 357 var tpp = this.parent.parent.buffer.buffer.duration / this.layer1.width;
nicholas@2481 358 for (var i = 0; i < this.comments.list.length; i++) {
nicholas@2479 359 var comment = this.comments.list[i];
nicholas@2481 360 var pixX = Math.floor(comment.time / tpp) + 0.5;
nicholas@2479 361 context.beginPath();
nicholas@2481 362 context.moveTo(pixX, 0);
nicholas@2481 363 context.lineTo(pixX, this.layer3.height);
nicholas@2479 364 context.stroke();
nicholas@2479 365 }
nicholas@2479 366 },
nicholas@2481 367 clearCanvas: function (canvas) {
nicholas@2479 368 var context = canvas.getContext("2d");
nicholas@2481 369 context.clearRect(0, 0, canvas.width, canvas.height);
nicholas@2479 370 }
nicholas@2702 371 };
nicholas@2479 372 this.canvas.layer1.className = "timeline-element-canvas canvas-layer1 canvas-disabled";
nicholas@2479 373 this.canvas.layer2.className = "timeline-element-canvas canvas-layer2";
nicholas@2479 374 this.canvas.layer3.className = "timeline-element-canvas canvas-layer3";
nicholas@2479 375 this.canvas.layer4.className = "timeline-element-canvas canvas-layer3";
nicholas@2809 376 this.canvas.layer1.height = "160";
nicholas@2809 377 this.canvas.layer2.height = "160";
nicholas@2809 378 this.canvas.layer3.height = "160";
nicholas@2809 379 this.canvas.layer4.height = "160";
nicholas@2809 380 var canvasDiv = document.createElement("div");
nicholas@2809 381 canvasDiv.appendChild(this.canvas.layer1);
nicholas@2809 382 canvasDiv.appendChild(this.canvas.layer2);
nicholas@2809 383 canvasDiv.appendChild(this.canvas.layer3);
nicholas@2809 384 canvasDiv.appendChild(this.canvas.layer4);
nicholas@2809 385 canvasHolder.appendChild(canvasDiv);
nicholas@2481 386 this.canvas.layer1.addEventListener("mousemove", this.canvas);
nicholas@2481 387 this.canvas.layer1.addEventListener("mouseleave", this.canvas);
nicholas@2481 388 this.canvas.layer1.addEventListener("click", this.canvas);
nicholas@2481 389
nicholas@2809 390 if (audioObject.specification.image) {
nicholas@2809 391 canvasDiv.style.width = "80%";
nicholas@2809 392 var image = document.createElement("img");
nicholas@2809 393 image.src = audioObject.specification.image;
nicholas@2809 394 image.className = "timeline-element-image";
nicholas@2809 395 canvasHolder.appendChild(image);
nicholas@2809 396 } else {
nicholas@2809 397 canvasDiv.style.width = "100%";
nicholas@2809 398 }
nicholas@2809 399
nicholas@2479 400 var canvasIntervalID = null;
nicholas@2481 401
nicholas@2479 402 this.playButton = {
nicholas@2479 403 parent: this,
nicholas@2479 404 DOM: document.createElement("button"),
nicholas@2481 405 handleEvent: function (event) {
nicholas@2479 406 var id = this.parent.parent.id;
nicholas@2479 407 var str = this.DOM.textContent;
nicholas@2479 408 if (str == "Play") {
nicholas@2479 409 audioEngineContext.play(id);
nicholas@2479 410 } else if (str == "Stop") {
nicholas@2479 411 audioEngineContext.stop();
nicholas@2479 412 }
nicholas@2479 413 }
nicholas@2702 414 };
nicholas@2481 415 this.playButton.DOM.addEventListener("click", this.playButton);
nicholas@2479 416 this.playButton.DOM.className = "timeline-button timeline-button-disabled";
nicholas@2479 417 this.playButton.DOM.disabled = true;
nicholas@2479 418 this.playButton.DOM.textContent = "Wait";
nicholas@2481 419
nicholas@2479 420 buttonHolder.appendChild(this.playButton.DOM);
nicholas@2481 421
nicholas@2481 422 this.resize = function () {
nicholas@2808 423 this.canvas.resize();
nicholas@2702 424 };
nicholas@2481 425
nicholas@2481 426 this.enable = function () {
nicholas@2481 427 // This is used to tell the interface object that playback of this node is ready
nicholas@2481 428 this.canvas.layer1.addEventListener("click", this.canvas);
nicholas@2479 429 this.canvas.layer1.className = "timeline-element-canvas canvas-layer1";
nicholas@2479 430 this.playButton.DOM.className = "timeline-button timeline-button-play";
nicholas@2479 431 this.playButton.DOM.textContent = "Play";
nicholas@2479 432 this.playButton.DOM.disabled = false;
nicholas@2481 433
nicholas@2479 434 this.canvas.drawWaveform();
nicholas@2481 435 };
nicholas@2481 436 this.updateLoading = function (progress) {
nicholas@2481 437 // progress is a value from 0 to 100 indicating the current download state of media files
nicholas@2479 438 progress = String(progress);
nicholas@2481 439 progress = progress.substr(0, 5);
nicholas@2481 440 this.playButton.DOM.textContent = "Loading: " + progress + '%';
nicholas@2481 441 };
nicholas@2481 442 this.startPlayback = function () {
nicholas@2479 443 // Called when playback has begun
n@2890 444 var animate = function () {
n@2890 445 this.canvas.drawTicker.call(this.canvas);
n@2890 446 if (this.playButton.DOM.textContent == "Stop") {
n@2890 447 window.requestAnimationFrame(animate);
n@2890 448 }
n@2890 449 }.bind(this);
nicholas@2479 450 this.playButton.DOM.textContent = "Stop";
nicholas@2726 451 interfaceContext.commentBoxes.highlightById(audioObject.id);
n@2890 452 canvasIntervalID = window.requestAnimationFrame(animate);
nicholas@2479 453 };
nicholas@2481 454 this.stopPlayback = function () {
nicholas@2479 455 // Called when playback has stopped. This gets called even if playback never started!
nicholas@2479 456 window.clearInterval(canvasIntervalID);
nicholas@2479 457 this.canvas.clearCanvas(this.canvas.layer2);
nicholas@2479 458 this.playButton.DOM.textContent = "Play";
nicholas@2726 459 var box = interfaceContext.commentBoxes.boxes.find(function (a) {
nicholas@2726 460 return a.id === audioObject.id;
nicholas@2726 461 });
nicholas@2726 462 if (box) {
nicholas@2726 463 box.highlight(false);
nicholas@2726 464 }
nicholas@2479 465 };
nicholas@2481 466 this.getValue = function () {
nicholas@2481 467 // Return the current value of the object. If there is no value, return 0
nicholas@2479 468 return 0;
nicholas@2481 469 };
nicholas@2481 470 this.getPresentedId = function () {
nicholas@2481 471 // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
nicholas@2479 472 return labelSpan.textContent;
nicholas@2481 473 };
nicholas@2481 474 this.canMove = function () {
nicholas@2481 475 // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale.
nicholas@2481 476 // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
nicholas@2479 477 return false;
nicholas@2481 478 };
nicholas@2481 479 this.exportXMLDOM = function (audioObject) {
nicholas@2481 480 // Called by the audioObject holding this element to export the interface <value> node.
nicholas@2481 481 // If there is no value node (such as outside reference), return null
nicholas@2481 482 // 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@2481 483 // Use storage.document.createElement('value'); to generate the XML node.
nicholas@2481 484 return null;
nicholas@2481 485 };
nicholas@2481 486 this.error = function () {
nicholas@2479 487 // If there is an error with the audioObject, this will be called to indicate a failure
nicholas@2702 488 };
nicholas@2702 489 }
nicholas@2479 490
nicholas@2481 491 function resizeWindow(event) {
nicholas@2481 492 // Called on every window resize event, use this to scale your page properly
nicholas@2481 493 for (var i = 0; i < audioEngineContext.audioObjects.length; i++) {
nicholas@2479 494 audioEngineContext.audioObjects[i].interfaceDOM.resize();
nicholas@2479 495 }
nicholas@2479 496 }
nicholas@2479 497
nicholas@2481 498 function buttonSubmitClick() {
nicholas@2702 499 if (audioEngineContext.timer.testStarted === false) {
nicholas@2481 500 interfaceContext.lightbox.post("Warning", 'You have not started the test! Please click play on a sample to begin the test!');
nicholas@2481 501 return;
nicholas@2481 502 }
nicholas@2724 503 var checks = testState.currentStateMap.interfaces[0].options,
nicholas@2651 504 canContinue = true;
nicholas@2825 505 if (interfaceContext.checkFragmentMinPlays() === false) {
nicholas@2825 506 return;
nicholas@2825 507 }
nicholas@3035 508 if (interfaceContext.checkCommentQuestions() === false) {
nicholas@3035 509 return;
nicholas@3035 510 }
nicholas@2481 511 for (var i = 0; i < checks.length; i++) {
nicholas@2481 512 var checkState = true;
nicholas@2481 513 if (checks[i].type == 'check') {
nicholas@2481 514 switch (checks[i].name) {
nicholas@2481 515 case 'fragmentPlayed':
nicholas@2481 516 //Check if all fragments have been played
n@2790 517 checkState = interfaceContext.checkAllPlayed(checks[i].errorMessage);
nicholas@2481 518 break;
nicholas@2481 519 case 'fragmentFullPlayback':
nicholas@2481 520 //Check if all fragments have played to their full length
n@2790 521 checkState = interfaceContext.checkFragmentsFullyPlayed(checks[i].errorMessage);
nicholas@2481 522 break;
nicholas@2481 523 case 'fragmentComments':
n@2790 524 checkState = interfaceContext.checkAllCommented(checks[i].errorMessage);
nicholas@2481 525 break;
nicholas@2481 526 default:
nicholas@2481 527 console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
nicholas@2481 528 break;
nicholas@2481 529 }
nicholas@2702 530 if (checkState === false) {
nicholas@2569 531 canContinue = false;
nicholas@2481 532 }
nicholas@2481 533 }
nicholas@2481 534 if (!canContinue) {
nicholas@2481 535 return;
nicholas@2481 536 }
nicholas@2481 537 }
nicholas@2481 538
nicholas@2481 539 if (canContinue) {
nicholas@2481 540 if (audioEngineContext.status == 1) {
nicholas@2481 541 var playback = document.getElementById('playback-button');
nicholas@2481 542 playback.click();
nicholas@2481 543 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
nicholas@2481 544 }
nicholas@2481 545 testState.advanceState();
nicholas@2481 546 }
nicholas@2479 547 }
nicholas@2479 548
nicholas@2481 549 function pageXMLSave(store, pageSpecification) {
nicholas@2481 550 // MANDATORY
nicholas@2481 551 // Saves a specific test page
nicholas@2481 552 // You can use this space to add any extra nodes to your XML <audioHolder> saves
nicholas@2481 553 // Get the current <page> information in store (remember to appendChild your data to it)
nicholas@2481 554 // pageSpecification is the current page node configuration
nicholas@2481 555 // To create new XML nodes, use storage.document.createElement();
nicholas@2481 556
nicholas@2481 557 for (var i = 0; i < audioEngineContext.audioObjects.length; i++) {
nicholas@2480 558 var id = audioEngineContext.audioObjects[i].specification.id;
nicholas@2480 559 var commentsList = audioEngineContext.audioObjects[i].interfaceDOM.comments.list;
nicholas@2480 560 var root = audioEngineContext.audioObjects[i].storeDOM;
nicholas@2481 561 for (var j = 0; j < commentsList.length; j++) {
nicholas@2480 562 commentsList[j].buildXML(root);
nicholas@2480 563 }
nicholas@2480 564 }
nicholas@2481 565 }