annotate interfaces/horizontal-sliders.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 c0022a09c4f6
rev   line source
n@1116 1 // Once this is loaded and parsed, begin execution
n@1116 2 loadInterface();
n@1116 3
n@1116 4 function loadInterface() {
n@1116 5 // Use this to do any one-time page / element construction. For instance, placing any stationary text objects,
n@1116 6 // holding div's, or setting up any nodes which are present for the entire test sequence
n@1116 7
n@1116 8 // The injection point into the HTML page
n@1116 9 interfaceContext.insertPoint = document.getElementById("topLevelBody");
n@1116 10 var testContent = document.createElement('div');
n@1116 11 testContent.id = 'testContent';
n@1116 12
n@1116 13 // Create the top div for the Title element
n@1116 14 var titleAttr = specification.title;
n@1116 15 var title = document.createElement('div');
n@1116 16 title.className = "title";
n@1116 17 title.align = "center";
n@1116 18 var titleSpan = document.createElement('span');
n@1116 19
n@1116 20 // Set title to that defined in XML, else set to default
n@1116 21 if (titleAttr != undefined) {
n@1116 22 titleSpan.textContent = titleAttr;
n@1116 23 } else {
n@1116 24 titleSpan.textContent = 'Listening test';
n@1116 25 }
n@1116 26 // Insert the titleSpan element into the title div element.
n@1116 27 title.appendChild(titleSpan);
n@1116 28
n@1116 29 var pagetitle = document.createElement('div');
n@1116 30 pagetitle.className = "pageTitle";
n@1116 31 pagetitle.align = "center";
n@1116 32 var titleSpan = document.createElement('span');
n@1116 33 titleSpan.id = "pageTitle";
n@1116 34 pagetitle.appendChild(titleSpan);
n@1116 35
n@1116 36 // Create Interface buttons!
n@1116 37 var interfaceButtons = document.createElement('div');
n@1116 38 interfaceButtons.id = 'interface-buttons';
n@1116 39 interfaceButtons.style.height = '25px';
n@1116 40
n@1116 41 // Create playback start/stop points
n@1116 42 var playback = document.createElement("button");
n@1116 43 playback.innerHTML = 'Stop';
n@1116 44 playback.id = 'playback-button';
n@1116 45 playback.style.float = 'left';
n@1116 46 // onclick function. Check if it is playing or not, call the correct function in the
n@1116 47 // audioEngine, change the button text to reflect the next state.
n@1116 48 playback.onclick = function() {
n@1116 49 if (audioEngineContext.status == 1) {
n@1116 50 audioEngineContext.stop();
n@1116 51 this.innerHTML = 'Stop';
n@1116 52 var time = audioEngineContext.timer.getTestTime();
n@1116 53 console.log('Stopped at ' + time); // DEBUG/SAFETY
n@1116 54 }
n@1116 55 };
n@1116 56 // Create Submit (save) button
n@1116 57 var submit = document.createElement("button");
n@1116 58 submit.innerHTML = 'Submit';
n@1116 59 submit.onclick = buttonSubmitClick;
n@1116 60 submit.id = 'submit-button';
n@1116 61 submit.style.float = 'left';
n@1116 62 // Append the interface buttons into the interfaceButtons object.
n@1116 63 interfaceButtons.appendChild(playback);
n@1116 64 interfaceButtons.appendChild(submit);
n@1116 65
n@1116 66 // Create a slider box
n@1116 67 var sliderBox = document.createElement('div');
n@1116 68 sliderBox.style.width = "100%";
n@1116 69 sliderBox.style.height = window.innerHeight - 200+12 + 'px';
n@1116 70 sliderBox.style.marginBottom = '10px';
n@1116 71 sliderBox.id = 'slider';
n@1116 72 var scaleHolder = document.createElement('div');
n@1116 73 scaleHolder.id = "scale-holder";
n@1116 74 scaleHolder.style.marginLeft = "107px";
n@1116 75 sliderBox.appendChild(scaleHolder);
n@1116 76 var scaleText = document.createElement('div');
n@1116 77 scaleText.id = "scale-text-holder";
n@1116 78 scaleText.style.height = "25px";
n@1116 79 scaleText.style.width = "100%";
n@1116 80 scaleHolder.appendChild(scaleText);
n@1116 81 var scaleCanvas = document.createElement('canvas');
n@1116 82 scaleCanvas.id = "scale-canvas";
n@1116 83 scaleCanvas.style.marginLeft = "100px";
n@1116 84 scaleHolder.appendChild(scaleCanvas);
n@1116 85 var sliderObjectHolder = document.createElement('div');
n@1116 86 sliderObjectHolder.id = 'slider-holder';
n@1116 87 sliderObjectHolder.align = "center";
n@1116 88 sliderBox.appendChild(sliderObjectHolder);
n@1116 89
n@1116 90 // Global parent for the comment boxes on the page
n@1116 91 var feedbackHolder = document.createElement('div');
n@1116 92 feedbackHolder.id = 'feedbackHolder';
n@1116 93
n@1116 94 testContent.style.zIndex = 1;
n@1116 95 interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
n@1116 96
n@1116 97 // Inject into HTML
n@1116 98 testContent.appendChild(title); // Insert the title
n@1116 99 testContent.appendChild(pagetitle);
n@1116 100 testContent.appendChild(interfaceButtons);
n@1116 101 testContent.appendChild(sliderBox);
n@1116 102 testContent.appendChild(feedbackHolder);
n@1116 103 interfaceContext.insertPoint.appendChild(testContent);
n@1116 104
n@1116 105 // Load the full interface
n@1116 106 testState.initialise();
n@1116 107 testState.advanceState();
n@1116 108 };
n@1116 109
n@1116 110 function loadTest(page)
n@1116 111 {
n@1116 112 // Called each time a new test page is to be build. The page specification node is the only item passed in
n@1116 113 var id = page.id;
n@1116 114
n@1116 115 var feedbackHolder = document.getElementById('feedbackHolder');
n@1116 116 feedbackHolder.innerHTML = null;
n@1116 117
n@1116 118 var interfaceObj = page.interfaces;
n@1116 119 if (interfaceObj.length > 1)
n@1116 120 {
n@1116 121 console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
n@1116 122 }
n@1116 123 interfaceObj = interfaceObj[0];
n@1116 124 if(interfaceObj.title != null)
n@1116 125 {
n@1116 126 document.getElementById("pageTitle").textContent = interfaceObj.title;
n@1116 127 }
n@1116 128
n@1116 129 var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
n@1116 130 for (var option of interfaceOptions)
n@1116 131 {
n@1116 132 if (option.type == "show")
n@1116 133 {
n@1116 134 switch(option.name) {
n@1116 135 case "playhead":
n@1116 136 var playbackHolder = document.getElementById('playback-holder');
n@1116 137 if (playbackHolder == null)
n@1116 138 {
n@1116 139 playbackHolder = document.createElement('div');
n@1116 140 playbackHolder.style.width = "100%";
n@1116 141 playbackHolder.align = 'center';
n@1116 142 playbackHolder.appendChild(interfaceContext.playhead.object);
n@1116 143 feedbackHolder.appendChild(playbackHolder);
n@1116 144 }
n@1116 145 break;
n@1116 146 case "page-count":
n@1116 147 var pagecountHolder = document.getElementById('page-count');
n@1116 148 if (pagecountHolder == null)
n@1116 149 {
n@1116 150 pagecountHolder = document.createElement('div');
n@1116 151 pagecountHolder.id = 'page-count';
n@1116 152 }
n@1116 153 pagecountHolder.innerHTML = '<span>Page '+(page.presentedId+1)+' of '+specification.pages.length+'</span>';
n@1116 154 var inject = document.getElementById('interface-buttons');
n@1116 155 inject.appendChild(pagecountHolder);
n@1116 156 break;
n@1116 157 case "volume":
n@1116 158 if (document.getElementById('master-volume-holder') == null)
n@1116 159 {
n@1116 160 feedbackHolder.appendChild(interfaceContext.volume.object);
n@1116 161 }
n@1116 162 break;
n@1116 163 }
n@1116 164 }
n@1116 165 }
n@1116 166
n@1116 167 // Delete outside reference
n@1116 168 var outsideReferenceHolder = document.getElementById('outside-reference');
n@1116 169 if (outsideReferenceHolder != null) {
n@1116 170 document.getElementById('interface-buttons').removeChild(outsideReferenceHolder);
n@1116 171 }
n@1116 172
n@1116 173 var sliderBox = document.getElementById('slider-holder');
n@1116 174 sliderBox.innerHTML = null;
n@1116 175
n@1116 176 var commentBoxPrefix = "Comment on track";
n@1116 177 if (interfaceObj.commentBoxPrefix != undefined) {
n@1116 178 commentBoxPrefix = interfaceObj.commentBoxPrefix;
n@1116 179 }
n@1116 180 var loopPlayback = page.loop;
n@1116 181
n@1116 182 $(page.commentQuestions).each(function(index,element) {
n@1116 183 var node = interfaceContext.createCommentQuestion(element);
n@1116 184 feedbackHolder.appendChild(node.holder);
n@1116 185 });
n@1116 186
n@1116 187 // Find all the audioElements from the audioHolder
n@1116 188 var label = 0;
n@1116 189 $(page.audioElements).each(function(index,element){
n@1116 190 // Find URL of track
n@1116 191 // In this jQuery loop, variable 'this' holds the current audioElement.
n@1116 192
n@1116 193 var audioObject = audioEngineContext.newTrack(element);
n@1116 194 if (element.type == 'outside-reference')
n@1116 195 {
n@1116 196 // Construct outside reference;
n@1116 197 var orNode = new outsideReferenceDOM(audioObject,index,document.getElementById('interface-buttons'));
n@1116 198 audioObject.bindInterface(orNode);
n@1116 199 } else {
n@1116 200 // Create a slider per track
n@1116 201 var sliderObj = new sliderObject(audioObject,label);
n@1116 202
n@1116 203 if (typeof page.initialPosition === "number")
n@1116 204 {
n@1116 205 // Set the values
n@1116 206 sliderObj.slider.value = page.initalPosition;
n@1116 207 } else {
n@1116 208 // Distribute it randomnly
n@1116 209 sliderObj.slider.value = Math.random();
n@1116 210 }
n@1116 211 sliderBox.appendChild(sliderObj.holder);
n@1116 212 audioObject.bindInterface(sliderObj);
n@1116 213 interfaceContext.createCommentBox(audioObject);
n@1116 214 label += 1;
n@1116 215 }
n@1116 216
n@1116 217 });
n@1116 218 if (page.showElementComments)
n@1116 219 {
n@1116 220 interfaceContext.showCommentBoxes(feedbackHolder,true);
n@1116 221 }
n@1116 222 // Auto-align
n@1116 223 resizeWindow(null);
n@1116 224 }
n@1116 225
n@1116 226 function sliderObject(audioObject,label)
n@1116 227 {
n@1116 228 // An example node, you can make this however you want for each audioElement.
n@1116 229 // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
n@1116 230 // You attach them by calling audioObject.bindInterface( )
n@1116 231 this.parent = audioObject;
n@1116 232
n@1116 233 this.holder = document.createElement('div');
n@1116 234 this.title = document.createElement('div');
n@1116 235 this.slider = document.createElement('input');
n@1116 236 this.play = document.createElement('button');
n@1116 237
n@1116 238 this.holder.className = 'track-slider';
n@1116 239 this.holder.style.width = window.innerWidth-200 + 'px';
n@1116 240 this.holder.appendChild(this.title);
n@1116 241 this.holder.appendChild(this.slider);
n@1116 242 this.holder.appendChild(this.play);
n@1116 243 this.holder.setAttribute('trackIndex',audioObject.id);
n@1116 244 this.title.textContent = label;
n@1116 245 this.title.className = 'track-slider-title';
n@1116 246
n@1116 247 this.slider.type = "range";
n@1116 248 this.slider.className = "track-slider-range track-slider-not-moved";
n@1116 249 this.slider.min = "0";
n@1116 250 this.slider.max = "1";
n@1116 251 this.slider.step = "0.01";
n@1116 252 this.slider.style.width = window.innerWidth-420 + 'px';
n@1116 253 this.slider.onchange = function()
n@1116 254 {
n@1116 255 var time = audioEngineContext.timer.getTestTime();
n@1116 256 var id = Number(this.parentNode.getAttribute('trackIndex'));
n@1116 257 audioEngineContext.audioObjects[id].metric.moved(time,this.value);
n@1116 258 console.log('slider '+id+' moved to '+this.value+' ('+time+')');
n@1116 259 $(this).removeClass('track-slider-not-moved');
n@1116 260 };
n@1116 261
n@1116 262 this.play.className = 'track-slider-button';
n@1116 263 this.play.textContent = "Loading...";
n@1116 264 this.play.value = audioObject.id;
n@1116 265 this.play.disabled = true;
n@1116 266 this.play.onclick = function(event)
n@1116 267 {
n@1116 268 var id = Number(event.currentTarget.value);
n@1116 269 //audioEngineContext.metric.sliderPlayed(id);
n@1116 270 audioEngineContext.play(id);
n@1116 271 };
n@1116 272 this.resize = function(event)
n@1116 273 {
n@1116 274 this.holder.style.width = window.innerWidth-200 + 'px';
n@1116 275 this.slider.style.width = window.innerWidth-420 + 'px';
n@1116 276 };
n@1116 277 this.enable = function()
n@1116 278 {
n@1116 279 // This is used to tell the interface object that playback of this node is ready
n@1116 280 this.play.disabled = false;
n@1116 281 this.play.textContent = "Play";
n@1116 282 $(this.slider).removeClass('track-slider-disabled');
n@1116 283 };
n@1116 284 this.updateLoading = function(progress)
n@1116 285 {
n@1116 286 // progress is a value from 0 to 100 indicating the current download state of media files
n@1116 287 };
n@1116 288 this.startPlayback = function()
n@1116 289 {
n@1116 290 // Called when playback has begun
n@1116 291 $(".track-slider").removeClass('track-slider-playing');
n@1116 292 $(this.holder).addClass('track-slider-playing');
n@1116 293 var outsideReference = document.getElementById('outside-reference');
n@1116 294 if (outsideReference != null) {
n@1116 295 $(outsideReference).removeClass('track-slider-playing');
n@1116 296 }
n@1116 297 };
n@1116 298 this.stopPlayback = function()
n@1116 299 {
n@1116 300 // Called when playback has stopped. This gets called even if playback never started!
n@1116 301 $(this.holder).removeClass('track-slider-playing');
n@1116 302 };
n@1116 303 this.getValue = function()
n@1116 304 {
n@1116 305 // Return the current value of the object. If there is no value, return 0
n@1116 306 return this.slider.value;
n@1116 307 };
n@1116 308 this.getPresentedId = function()
n@1116 309 {
n@1116 310 // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
n@1116 311 return this.title.textContent;
n@1116 312 };
n@1116 313 this.canMove = function()
n@1116 314 {
n@1116 315 // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale.
n@1116 316 // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
n@1116 317 return true;
n@1116 318 };
n@1116 319 this.exportXMLDOM = function(audioObject) {
n@1116 320 // Called by the audioObject holding this element to export the interface <value> node.
n@1116 321 // If there is no value node (such as outside reference), return null
n@1116 322 // 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
n@1116 323 // Use storage.document.createElement('value'); to generate the XML node.
n@1116 324 var node = storage.document.createElement('value');
n@1116 325 node.textContent = this.slider.value;
n@1116 326 return node;
n@1116 327 };
n@1116 328 };
n@1116 329
n@1116 330 function outsideReferenceDOM(audioObject,index,inject)
n@1116 331 {
n@1116 332 this.parent = audioObject;
n@1116 333 this.outsideReferenceHolder = document.createElement('button');
n@1116 334 this.outsideReferenceHolder.id = 'outside-reference';
n@1116 335 this.outsideReferenceHolder.className = 'outside-reference';
n@1116 336 this.outsideReferenceHolder.setAttribute('track-id',index);
n@1116 337 this.outsideReferenceHolder.textContent = "Play Reference";
n@1116 338 this.outsideReferenceHolder.disabled = true;
n@1116 339
n@1116 340 this.outsideReferenceHolder.onclick = function(event)
n@1116 341 {
n@1116 342 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
n@1116 343 };
n@1116 344 inject.appendChild(this.outsideReferenceHolder);
n@1116 345 this.enable = function()
n@1116 346 {
n@1116 347 if (this.parent.state == 1)
n@1116 348 {
n@1116 349 this.outsideReferenceHolder.disabled = false;
n@1116 350 }
n@1116 351 };
n@1116 352 this.updateLoading = function(progress)
n@1116 353 {
n@1116 354 if (progress != 100)
n@1116 355 {
n@1116 356 progress = String(progress);
n@1116 357 progress = progress.split('.')[0];
n@1116 358 this.outsideReferenceHolder[0].children[0].textContent = progress+'%';
n@1116 359 } else {
n@1116 360 this.outsideReferenceHolder[0].children[0].textContent = "Play Reference";
n@1116 361 }
n@1116 362 };
n@1116 363 this.startPlayback = function()
n@1116 364 {
n@1116 365 // Called when playback has begun
n@1116 366 $('.track-slider').removeClass('track-slider-playing');
n@1116 367 $('.comment-div').removeClass('comment-box-playing');
n@1116 368 $(this.outsideReferenceHolder).addClass('track-slider-playing');
n@1116 369 };
n@1116 370 this.stopPlayback = function()
n@1116 371 {
n@1116 372 // Called when playback has stopped. This gets called even if playback never started!
n@1116 373 $(this.outsideReferenceHolder).removeClass('track-slider-playing');
n@1116 374 };
n@1116 375 this.exportXMLDOM = function(audioObject)
n@1116 376 {
n@1116 377 return null;
n@1116 378 };
n@1116 379 this.getValue = function()
n@1116 380 {
n@1116 381 return 0;
n@1116 382 };
n@1116 383 this.getPresentedId = function()
n@1116 384 {
n@1116 385 return 'reference';
n@1116 386 };
n@1116 387 this.canMove = function()
n@1116 388 {
n@1116 389 return false;
n@1116 390 };
n@1116 391 }
n@1116 392
n@1116 393 function resizeWindow(event)
n@1116 394 {
n@1116 395 // Called on every window resize event, use this to scale your page properly
n@1116 396
n@1116 397 var numObj = document.getElementsByClassName('track-slider').length;
n@1116 398 var totalHeight = (numObj * 125)-25;
n@1116 399 document.getElementById('scale-holder').style.width = window.innerWidth-220 + 'px';
n@1116 400 var canvas = document.getElementById('scale-canvas');
n@1116 401 canvas.width = window.innerWidth-420;
n@1116 402 canvas.height = totalHeight;
n@1116 403 for (var i in audioEngineContext.audioObjects)
n@1116 404 {
n@1116 405 if (audioEngineContext.audioObjects[i].specification.type != 'outside-reference'){
n@1116 406 audioEngineContext.audioObjects[i].interfaceDOM.resize(event);
n@1116 407 }
n@1116 408 }
n@1116 409 document.getElementById("slider").style.height = totalHeight+50+'px';
n@1116 410 drawScale();
n@1116 411 }
n@1116 412
n@1116 413 function drawScale()
n@1116 414 {
n@1116 415 var interfaceObj = testState.currentStateMap.interfaces[0];
n@1116 416 var scales = testState.currentStateMap.interfaces[0].scales;
n@1116 417 scales = scales.sort(function(a,b) {
n@1116 418 return a.position - b.position;
n@1116 419 });
n@1116 420 var canvas = document.getElementById('scale-canvas');
n@1116 421 var ctx = canvas.getContext("2d");
n@1116 422 var height = canvas.height;
n@1116 423 var width = canvas.width;
n@1116 424 var textHolder = document.getElementById('scale-text-holder');
n@1116 425 textHolder.innerHTML = null;
n@1116 426 ctx.fillStyle = "#000000";
n@1116 427 ctx.setLineDash([1,4]);
n@1116 428 for (var scale of scales)
n@1116 429 {
n@1116 430 var posPercent = scale.position / 100.0;
n@1116 431 var posPix = Math.round(width * posPercent);
n@1116 432 if(posPix<=0){posPix=1;}
n@1116 433 if(posPix>=width){posPix=width-1;}
n@1116 434 ctx.moveTo(posPix,0);
n@1116 435 ctx.lineTo(posPix,height);
n@1116 436 ctx.stroke();
n@1116 437
n@1116 438 var text = document.createElement('div');
n@1116 439 text.align = "center";
n@1116 440 var textC = document.createElement('span');
n@1116 441 textC.textContent = scale.text;
n@1116 442 text.appendChild(textC);
n@1116 443 text.className = "scale-text";
n@1116 444 textHolder.appendChild(text);
n@1116 445 text.style.width = Math.ceil($(text).width())+'px';
n@1116 446 text.style.left = (posPix+100-($(text).width()/2)) +'px';
n@1116 447 }
n@1116 448 }
n@1116 449
n@1116 450 function buttonSubmitClick() // TODO: Only when all songs have been played!
n@1116 451 {
n@1116 452 var checks = [];
n@1116 453 checks = checks.concat(testState.currentStateMap.interfaces[0].options);
n@1116 454 checks = checks.concat(specification.interfaces.options);
n@1116 455 var canContinue = true;
n@1116 456
n@1116 457 // Check that the anchor and reference objects are correctly placed
n@1116 458 if (interfaceContext.checkHiddenAnchor() == false) {return;}
n@1116 459 if (interfaceContext.checkHiddenReference() == false) {return;}
n@1116 460
n@1116 461 for (var i=0; i<checks.length; i++) {
n@1116 462 if (checks[i].type == 'check')
n@1116 463 {
n@1116 464 switch(checks[i].name) {
n@1116 465 case 'fragmentPlayed':
n@1116 466 // Check if all fragments have been played
n@1116 467 var checkState = interfaceContext.checkAllPlayed();
n@1116 468 if (checkState == false) {canContinue = false;}
n@1116 469 break;
n@1116 470 case 'fragmentFullPlayback':
n@1116 471 // Check all fragments have been played to their full length
n@1116 472 var checkState = interfaceContext.checkAllPlayed();
n@1116 473 if (checkState == false) {canContinue = false;}
n@1116 474 console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
n@1116 475 break;
n@1116 476 case 'fragmentMoved':
n@1116 477 // Check all fragment sliders have been moved.
n@1116 478 var checkState = interfaceContext.checkAllMoved();
n@1116 479 if (checkState == false) {canContinue = false;}
n@1116 480 break;
n@1116 481 case 'fragmentComments':
n@1116 482 // Check all fragment sliders have been moved.
n@1116 483 var checkState = interfaceContext.checkAllCommented();
n@1116 484 if (checkState == false) {canContinue = false;}
n@1116 485 break;
n@1116 486 //case 'scalerange':
n@1116 487 // Check the scale is used to its full width outlined by the node
n@1116 488 //var checkState = interfaceContext.checkScaleRange();
n@1116 489 //if (checkState == false) {canContinue = false;}
n@1116 490 // break;
n@1116 491 default:
n@1116 492 console.log("WARNING - Check option "+checks[i].check+" is not supported on this interface");
n@1116 493 break;
n@1116 494 }
n@1116 495
n@1116 496 }
n@1116 497 if (!canContinue) {break;}
n@1116 498 }
n@1116 499
n@1116 500 if (canContinue) {
n@1116 501 if (audioEngineContext.status == 1) {
n@1116 502 var playback = document.getElementById('playback-button');
n@1116 503 playback.click();
n@1116 504 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
n@1116 505 } else
n@1116 506 {
n@1116 507 if (audioEngineContext.timer.testStarted == false)
n@1116 508 {
n@1116 509 alert('You have not started the test! Please press start to begin the test!');
n@1116 510 return;
n@1116 511 }
n@1116 512 }
n@1116 513 testState.advanceState();
n@1116 514 }
n@1116 515 }
n@1116 516
n@1116 517 function pageXMLSave(store, pageSpecification)
n@1116 518 {
n@1116 519 // MANDATORY
n@1116 520 // Saves a specific test page
n@1116 521 // You can use this space to add any extra nodes to your XML <audioHolder> saves
n@1116 522 // Get the current <page> information in store (remember to appendChild your data to it)
n@1116 523 // pageSpecification is the current page node configuration
n@1116 524 // To create new XML nodes, use storage.document.createElement();
n@1116 525 }