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 }
|