1// Igor 2jQuery.storedPeaks = []; 3 4/** 5 * Retrieves audio from an external source, the initializes the drawing function 6 * @param {String} url the url of the audio we'd like to fetch 7 */ 8var qtDrawAudio = url => { 9 // Set up audio context 10 window.AudioContext = window.AudioContext || window.webkitAudioContext; 11 const audioContext = new AudioContext(); 12 jQuery('#qtmplayerTrackControl canvas').remove(); 13 jQuery('#qtmplayerTrackControl').append('<canvas></canvas>'); 14 jQuery.qtmplayerCanvas = jQuery('#qtmplayerTrackControl canvas'); 15 jQuery('#qtmplayerTrackControl canvas').css({position: "absolute", 'top':0,'left':0,'width':'100%','height':'100%'}); 16 17 if( undefined === jQuery.storedPeaks[url] ){ 18 var storedSongPeaks = false; 19 // first try to get peaks from the database 20 jQuery.ajax({ 21 type: "post", 22 url: ajax_var.url, 23 data: { 24 'action': 'qtmplayer-get-peaks', 25 'nonce': ajax_var.nonce, 26 'url': url 27 }, 28 success: function( response ){ 29 if(!response){ 30 fetch(url) 31 .then(response => response.arrayBuffer()) 32 .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer)) 33 .then(audioBuffer => draw(url,normalizeData(filterData(audioBuffer)),true)); // third parameter: store the data? 34 } else { 35 var peaks = jQuery.parseJSON(response ); 36 jQuery.storedPeaks[url] = peaks; 37 draw(url, peaks, false); 38 } 39 40 }, 41 fail: function(e){ 42 console.log( e ); 43 } 44 }); 45 } else { 46 draw(url, jQuery.storedPeaks[url], false); 47 } 48}; 49// console.log('qtDrawAudio type: '+typeof(qtDrawAudio)); 50 51/** 52 * Filters the AudioBuffer retrieved from an external source 53 * @param {AudioBuffer} audioBuffer the AudioBuffer from drawAudio() 54 * @returns {Array} an array of floating point numbers 55 */ 56var filterData = audioBuffer => { 57 const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data 58 const samples = 140; // Number of samples we want to have in our final data set 59 const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision 60 const filteredData = []; 61 for (let i = 0; i < samples; i++) { 62 let blockStart = blockSize * i; // the location of the first sample in the block 63 let sum = 0; 64 for (let j = 0; j < blockSize; j++) { 65 sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block 66 } 67 filteredData.push(sum / blockSize); // divide the sum by the block size to get the average 68 } 69 return filteredData; 70}; 71 72/** 73 * Normalizes the audio data to make a cleaner illustration 74 * @param {Array} filteredData the data from filterData() 75 * @returns {Array} an normalized array of floating point numbers 76 */ 77var normalizeData = filteredData => { 78 const multiplier = Math.pow(Math.max(...filteredData), -1); 79 let normalized = filteredData.map(n => n * multiplier); 80 return normalized; 81} 82 83 84 85/** 86 * Draws the audio file into a canvas element. 87 * @param {Array} normalizedData The filtered array returned from filterData() 88 * @returns {Array} a normalized array of data 89 */ 90var draw = ( url, normalizedData, storeData) => { 91 const canvas = jQuery.qtmplayerCanvas[0];//document.querySelector("canvas"); 92 const dpr = window.devicePixelRatio || 1; 93 const padding = 14; 94 canvas.width = canvas.offsetWidth * dpr; 95 canvas.height = (canvas.offsetHeight + padding * 2) * dpr; 96 const ctx = canvas.getContext("2d"); 97 ctx.scale(dpr, dpr); 98 ctx.translate(0, canvas.offsetHeight / 2 + padding); // set Y = 0 to be in the middle of the canvas 99 // draw the line segments 100 const width = canvas.offsetWidth / normalizedData.length; 101 jQuery.storedPeaks[url] = normalizedData;// Save the peaks associated with the URL to avoid reloading the same file again 102 103 104 if(true == storeData){ 105 jQuery.ajax({ 106 type: "post", 107 url: ajax_var.url, 108 data: { 109 'action': 'qtmplayer-store-peaks', 110 'nonce': ajax_var.nonce, 111 'url': url, 112 'peaks': normalizedData 113 }, 114 success: function( response ){ 115 return true; 116 }, 117 fail: function(e){ 118 console.log( e ); 119 return false; 120 } 121 }); 122 } 123 for (let i = 0; i < normalizedData.length; i++) { 124 const x = width * i; 125 let height = normalizedData[i] * canvas.offsetHeight - padding; 126 if (height < 0) { 127 height = 0; 128 } else if (height > canvas.offsetHeight / 2) { 129 height = height > canvas.offsetHeight / 2; 130 } 131 drawLineSegment(ctx, x, height, width, (i + 1) % 2); 132 } 133}; 134 135 136 137 138 139 140/** 141 * A utility function for drawing our line segments 142 * @param {AudioContext} ctx the audio context 143 * @param {number} x the x coordinate of the beginning of the line segment 144 * @param {number} height the desired height of the line segment 145 * @param {number} width the desired width of the line segment 146 * @param {boolean} isEven whether or not the segmented is even-numbered 147 */ 148var drawLineSegment = (ctx, x, height, width, isEven) => { 149 ctx.lineWidth = 1; // how thick the line is 150 ctx.strokeStyle = "rgba(150,150,150,0.8)"; // what color our line is 151 ctx.beginPath(); 152 height = isEven ? height : -height; 153 ctx.moveTo(x, 0); 154 ctx.lineTo(x, height); 155 ctx.arc(x + width / 2, height, width / 2, Math.PI, 0, isEven); 156 ctx.lineTo(x + width, 0); 157 ctx.stroke(); 158}; 159
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.