1// Igor 2jQuery.storedPeaks = []; 3 4 5function qtWaveformDebug(m){ 6 if( jQuery('#qtmusicplayer').data('debug') ){ 7 var c = jQuery("#qtmPlayerDebugger"); 8 if(c.length > 0){ 9 c.append(m+'<br>'); 10 } 11 } 12} 13 14 15/** 16 * Retrieves audio from an external source, the initializes the drawing function 17 * @param {String} url the url of the audio we'd like to fetch 18 */ 19var qtDrawAudio = url => { 20 21 if(jQuery('#qtmplayerWaveform').length <= 0){ 22 return; 23 } 24 // Set up audio context 25 window.AudioContext = window.AudioContext || window.webkitAudioContext; 26 const audioContext = new AudioContext(); 27 jQuery('#qtmplayerWaveform canvas').remove(); 28 jQuery('#qtmplayerWaveform').append('<canvas id="qtwaveformOriginal"></canvas><canvas id="qtwaveformClone"></canvas>');//.append('<canvas></canvas>'); 29 jQuery.qtmplayerCanvas = jQuery('#qtmplayerWaveform #qtwaveformOriginal'); 30 jQuery.qtmplayerCanvasClone = jQuery('#qtmplayerWaveform #qtwaveformClone'); 31 jQuery.qtmplayerCanvasColor = jQuery('#qtmplayerWaveform').data('qtmplayer-color'); 32 jQuery.qtmplayerCanvasCloneColor = jQuery('#qtmplayerWaveform').data('qtmplayer-color-clone'); 33 jQuery('#qtmplayerTrackControl canvas').css({position: "absolute", 'top':0,'left':0,'width':'100%','height':'100%'}); 34 35 if( undefined === jQuery.storedPeaks[url] ){ 36 var storedSongPeaks = false; 37 /** 38 * first try to get peaks from the database 39 */ 40 jQuery.ajax({ 41 type: "post", 42 url: qtmplayer_ajax_var.url, 43 data: { 44 'action': 'qtmplayer-get-peaks', 45 'nonce': qtmplayer_ajax_var.peaksnonce, 46 'url': url 47 }, 48 success: function( response ){ 49 if(!response || response == '' || response == null){ 50 51 fetch(url) 52 .then(response => response.arrayBuffer()) 53 .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer)) 54 .then(audioBuffer => draw(url,normalizeData(filterData(audioBuffer)),true)); // third parameter: store the data? 55 } else { 56 var peaks = jQuery.parseJSON(response ); 57 jQuery.storedPeaks[url] = peaks; 58 draw(url, peaks, false); 59 } 60 }, 61 fail: function(e){ 62 console.log( e ); 63 } 64 }); 65 } else { 66 draw(url, jQuery.storedPeaks[url], false); 67 } 68}; 69 70 71 72 73 74 75 76 77 78 79 80 81/** 82 * Filters the AudioBuffer retrieved from an external source 83 * @param {AudioBuffer} audioBuffer the AudioBuffer from drawAudio() 84 * @returns {Array} an array of floating point numbers 85 */ 86var filterData = audioBuffer => { 87 const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data 88 let samples = 120; 89 if( jQuery('#qtmplayer').data('qtmplayer-detailedwaveform') == 1 ){ 90 samples = 271; // Number of samples we want to have in our final data set 91 } 92 93 const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision 94 const filteredData = []; 95 for (let i = 0; i < samples; i++) { 96 let blockStart = blockSize * i; // the location of the first sample in the block 97 let sum = 0; 98 for (let j = 0; j < blockSize; j++) { 99 sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block 100 } 101 filteredData.push(sum / blockSize); // divide the sum by the block size to get the average 102 } 103 return filteredData; 104}; 105 106/** 107 * Normalizes the audio data to make a cleaner illustration 108 * @param {Array} filteredData the data from filterData() 109 * @returns {Array} an normalized array of floating point numbers 110 */ 111var normalizeData = filteredData => { 112 const multiplier = Math.pow(Math.max(...filteredData), -1); 113 let normalized = filteredData.map(n => (n * multiplier ).toFixed(2) ); 114 return normalized; 115} 116 117 118 119/** 120 * Draws the audio file into a canvas element. 121 * @param {Array} normalizedData The filtered array returned from filterData() 122 * @returns {Array} a normalized array of data 123 */ 124const drawDetail = ( url, normalizedData, storeData) => { 125 126 127 const dpr = window.devicePixelRatio || 1; 128 const gap = 1; 129 const padding = 1; 130 let x; 131 jQuery.storedPeaks[url] = normalizedData;// Save the peaks associated with the URL to avoid reloading the same file again 132 133 134 135 const canvas = jQuery.qtmplayerCanvas[0];; 136 canvas.width = canvas.offsetWidth * dpr; 137 canvas.height = (canvas.offsetHeight + padding * 2) * dpr; 138 const ctx = canvas.getContext("2d"); 139 ctx.scale(dpr, -dpr); 140 ctx.translate(0, - canvas.offsetHeight - padding- padding ); // set Y = 0 to be in the middle of the canvas
141 const width = (canvas.offsetWidth / normalizedData.length).toFixed(3); 142 143 144 145 ctx.beginPath(); 146 ctx.moveTo( 0, 0 ); 147 for ( let i = 0; i < normalizedData.length; i++ ) { 148 let x = width * i; 149 let height = normalizedData[i] * canvas.offsetHeight - padding +2; 150 ctx.moveTo( x, 0 ); 151 ctx.lineTo( x, height ); 152 ctx.lineTo( Math.round(width - gap + x) , height); 153 ctx.lineTo( Math.round(width - gap + x), 0); 154 ctx.lineTo( Math.round(width + x) , 0); 155 } 156 // Clone for tracking 157 ctx.moveTo( 0, 0 ); 158 ctx.fillStyle= jQuery.qtmplayerCanvasColor; 159 ctx.fill(); 160 161 162 // ========================================================================================================== 163 // The clone 164 // ========================================================================================================== 165 const canvasClone = jQuery.qtmplayerCanvasClone[0];//document.querySelector("canvas"); 166 canvasClone.width = canvasClone.offsetWidth * dpr; 167 canvasClone.height = (canvasClone.offsetHeight + padding * 2) * dpr; 168 let ctxClone = canvasClone.getContext("2d"); 169 ctxClone.scale(dpr, -dpr); 170 ctxClone.translate(0, - canvasClone.offsetHeight - padding - padding ); // set Y = 0 to be in the middle of the canvas 171 ctxClone.beginPath(); 172 ctxClone.moveTo( 0, 0 ); 173 // Draw the upper path. 174 for ( let i = 0; i < normalizedData.length; i++ ) { 175 let x = width * i; 176 let height = normalizedData[i] * canvas.offsetHeight - padding +2; 177 178 ctxClone.moveTo( x, 0 ); 179 ctxClone.lineTo( x, height ); 180 ctxClone.lineTo( Math.round(width - gap + x) , height); 181 ctxClone.lineTo( Math.round(width - gap + x), 0); 182 ctxClone.lineTo( Math.round(width + x) , 0); 183 } 184 // Clone for tracking 185 ctxClone.moveTo( 0, 0 ); 186 187 ctxClone.fillStyle = jQuery.qtmplayerCanvasCloneColor; 188 ctxClone.fill(); 189 190 191 let trackContainer = jQuery('[data-qtmplayer-file="'+url+'"]').closest('.qtmplayer-track-inpage'); 192 // console.log(url); 193 if( trackContainer.length > 0 ){ 194 trackContainer.each(function(i,c){ 195 var thisTrackContainer = jQuery(c); 196 thisTrackContainer.find('canvas').remove(); 197 thisTrackContainer.css({'position':'relative'}).append('<canvas>'); 198 jQuery.qtmplayeCurrentCanvas = thisTrackContainer.find('canvas'); 199 jQuery.qtmplayeCurrentCanvas.css({"position": "absolute", "top": 0, "left": '0', "width": "100%", "height": "100%", 'background':'transparent', 'pointer-events':'none'}); 200 var canvasC = jQuery.qtmplayeCurrentCanvas[0]; 201 canvasC.width = canvasC.offsetWidth * dpr; 202 canvasC.height = (canvasC.offsetHeight + padding * 2) * dpr; 203 var ctxC = canvasC.getContext("2d"); 204 ctxC.scale(dpr, -dpr); 205 ctxC.translate(0, - canvasC.offsetHeight - padding- padding ); // set Y = 0 to be in the middle of the canvas 206 var widthC = (canvasC.offsetWidth / normalizedData.length).toFixed(1) ; 207 ctxC.beginPath(); 208 ctxC.moveTo( 0, 0 ); 209 for ( let i = 0; i < normalizedData.length; i++ ) { 210 let x = widthC * i; 211 let height = normalizedData[i] * canvasC.offsetHeight - padding + 3; 212 ctxC.moveTo( x, 0 ); 213 ctxC.lineTo( x, height ); 214 ctxC.lineTo( Math.round(widthC - gap + x) , height); 215 ctxC.lineTo( Math.round(widthC - gap + x), 0); 216 ctxC.lineTo( widthC + x , 0); 217 } 218 // Clone for tracking 219 ctxC.moveTo( 0, 0 ); 220 ctxC.fillStyle= "rgba(210,210,210,0.5)"; 221 ctxC.fill(); 222 }); 223 224 225 } 226 227 228 229 // CREATE CACHE =========================================================================================== 230 if(true == storeData){ 231 jQuery.ajax({ 232 type: "post", 233 url: qtmplayer_ajax_var.url, 234 data: { 235 'action': 'qtmplayer-store-peaks', 236 'nonce': qtmplayer_ajax_var.peaksnonce, 237 'url': url, 238 'peaks': normalizedData 239 }, 240 success: function( response ){ 241 return true; 242 }, 243 fail: function(e){ 244 console.log( e ); 245 return false; 246 } 247 }); 248 } 249}; 250 251 252 253 254 255 256 257 258/** 259 * A utility function for drawing our line segments 260 * @param {AudioContext} ctx the audio context 261 * @param {number} x the x coordinate of the beginning of the line segment 262 * @param {number} height the desired height of the line segment 263 * @param {number} width the desired width of the line segment 264 * @param {boolean} isEven whether or not the segmented is even-numbered 265 */ 266var drawLineSegment = (ctx, x, height, width, isEven, color) => { 267 ctx.lineWidth = 1; // how thick the line is 268 ctx.strokeStyle =color; // what color our line is 269 ctx.beginPath(); 270 height = isEven ? height : -height; 271 ctx.moveTo(x, 0); 272 ctx.lineTo(x, height); 273 ctx.arc(x + width / 2, height, width / 2, Math.PI, 0, isEven); 274 ctx.lineTo(x + width, 0); 275 ctx.stroke(); 276}; 277 278 279 280 281/** 282 * Draws the audio file into a canvas element. 283 * @param {Array} normalizedData The filtered array returned from filterData() 284 * @returns {Array} a normalized array of data 285 */ 286var draw = ( url, normalizedData, storeData) => { 287 const UseDetailWaveform = jQuery('#qtmplayer').data('qtmplayer-detailedwaveform'); 288 if(UseDetailWaveform == 1){ 289 drawDetail( url, normalizedData, storeData ); 290 return; 291 } 292 const canvas = jQuery.qtmplayerCanvas[0];//document.querySelector("canvas"); 293 const dpr = window.devicePixelRatio || 1; 294 const padding = 14; 295 canvas.width = canvas.offsetWidth * dpr; 296 canvas.height = (canvas.offsetHeight + padding * 2) * dpr; 297 const ctx = canvas.getContext("2d"); 298 ctx.scale(dpr, dpr); 299 ctx.translate(0, canvas.offsetHeight / 2 + padding); // set Y = 0 to be in the middle of the canvas 300 // draw the line segments
301 const width = canvas.offsetWidth / normalizedData.length; 302 jQuery.storedPeaks[url] = normalizedData;// Save the peaks associated with the URL to avoid reloading the same file again 303 304 305 if(true == storeData){ 306 jQuery.ajax({ 307 type: "post", 308 url: qtmplayer_ajax_var.url, 309 data: { 310 'action': 'qtmplayer-store-peaks', 311 'nonce': qtmplayer_ajax_var.peaksnonce, 312 'url': url, 313 'peaks': normalizedData 314 }, 315 success: function( response ){ 316 return true; 317 }, 318 fail: function(e){ 319 console.log( e ); 320 return false; 321 } 322 }); 323 } 324 for (let i = 0; i < normalizedData.length; i++) { 325 const x = width * i; 326 let height = normalizedData[i] * canvas.offsetHeight - padding; 327 if (height < 0) { 328 height = 0; 329 } else if (height > canvas.offsetHeight / 2) { 330 height = height > canvas.offsetHeight / 2; 331 } 332 drawLineSegment(ctx, x, height, width, (i + 1) % 2, jQuery.qtmplayerCanvasColor); 333 } 334 335 336 // ========================================================================================================== 337 // The clone 338 // ========================================================================================================== 339 const canvasClone = jQuery.qtmplayerCanvasClone[0];//document.querySelector("canvas"); 340 canvasClone.width = canvasClone.offsetWidth * dpr; 341 canvasClone.height = (canvasClone.offsetHeight + padding * 2) * dpr; 342 const ctxClone = canvasClone.getContext("2d"); 343 ctxClone.scale(dpr, dpr); 344 ctxClone.translate(0, canvas.offsetHeight / 2 + padding); // set Y = 0 to be in the middle of the canvas 345 346 // Draw the upper path. 347 for ( let i = 0; i < normalizedData.length; i++ ) { 348 const x = width * i; 349 let height = normalizedData[i] * canvas.offsetHeight - padding; 350 if (height < 0) { 351 height = 0; 352 } else if (height > canvas.offsetHeight / 2) { 353 height = height > canvas.offsetHeight / 2; 354 } 355 drawLineSegment(ctxClone, x, height, width, (i + 1) % 2, jQuery.qtmplayerCanvasCloneColor); 356 } 357 358}; 359 360 361 362 363 364 365/** 366 * A utility function for drawing our line segments 367 * @param {AudioContext} ctx the audio context 368 * @param {number} x the x coordinate of the beginning of the line segment 369 * @param {number} height the desired height of the line segment 370 * @param {number} width the desired width of the line segment 371 * @param {boolean} isEven whether or not the segmented is even-numbered 372 */ 373var drawLineSegment = (ctx, x, height, width, isEven, color) => { 374 ctx.lineWidth = 1; // how thick the line is 375 ctx.strokeStyle =color; // what color our line is 376 ctx.beginPath(); 377 height = isEven ? height : -height; 378 ctx.moveTo(x, 0); 379 ctx.lineTo(x, height); 380 ctx.arc(x + width / 2, height, width / 2, Math.PI, 0, isEven); 381 ctx.lineTo(x + width, 0); 382 ctx.stroke(); 383};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.