1/** 2* Copyright 2020 QantumThemes by Igor Nardo - All Rights Reserved 3*/ 4jQuery.storedPeaks = []; 5 6function qtWaveformDebug(m){ 7 if( jQuery('#qtmusicplayer').data('debug') ){ 8 // console.log(infor); 9 var c = jQuery("#qtmPlayerDebugger"); 10 if(c.length > 0){ 11 c.append(m+'<br>'); 12 } 13 } 14} 15 16/** 17 * Retrieves audio from an external source, the initializes the drawing function 18 * @param {String} url the url of the audio we'd like to fetch 19 */ 20const drawAudio = url => { 21 22 if(0 === jQuery('#qtKenthaPlayerWaveform').length ) return; // do nothing if 23 24 jQuery('#qtKenthaPlayerWaveform canvas').remove(); 25 jQuery('#qtKenthaPlayerWaveform').append('<canvas id="qtwaveformOriginal"></canvas><canvas id="qtwaveformClone"></canvas>'); 26 jQuery.qtmplayerCanvas = jQuery('#qtKenthaPlayerWaveform #qtwaveformOriginal'); 27 jQuery.qtmplayerCanvasClone = jQuery('#qtKenthaPlayerWaveform #qtwaveformClone'); 28 jQuery.qtmplayerCanvasCloneColor = jQuery('#qtKenthaPlayerWaveform').data('color'); // not in use 29 if( undefined === jQuery.storedPeaks[url] ){ 30 var storedSongPeaks = false; 31 var ajaxPeaksUrl = jQuery('#qtmusicplayer').data('siteurl'); // ajax_var.url; 32 jQuery.ajax({ 33 type: "get", 34 url: ajaxPeaksUrl, 35 data: { 36 'kentha_spectrum_url' : url, 37 }, 38 success: function( response ){ 39 qtWaveformDebug('Waveform cache response success'); 40 var valid = 0; 41 if( 'undefined' !== typeof(response) ){ 42 if(response !== 'error'){ 43 try{ 44 peaks = jQuery.parseJSON(response); 45 if(typeof(peaks) == 'object') { 46 valid = 1; 47 } 48 } catch(e){ 49 console.log(e); 50 } 51 } 52 } 53 if( 1 === valid ){ 54 qtWaveformDebug('Use DB peaks'); 55 var peaks = jQuery.parseJSON(response ); 56 jQuery.storedPeaks[url] = peaks; 57 draw(url, peaks, false); 58 } else { 59 qtWaveformDebug('Calculate peaks new'); 60 window.AudioContext = window.AudioContext || window.webkitAudioContext; 61 const audioContext = new AudioContext(); 62 fetch(url) 63 .then(response => response.arrayBuffer()) 64 .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer)) 65 .then(audioBuffer => draw(url,normalizeData(filterData(audioBuffer)),true)); // third parameter: store the data? 66 } 67 return true; 68 }, fail: function(e){ 69 qtWaveformDebug( e ); 70 qtWaveformDebug('Ajax FAIL'); 71 } 72 }); 73 } else { 74 draw(url, jQuery.storedPeaks[url], false); 75 return true; 76 } 77}; 78 79/** 80 * Filters the AudioBuffer retrieved from an external source 81 * @param {AudioBuffer} audioBuffer the AudioBuffer from drawAudio() 82 * @returns {Array} an array of floating point numbers 83 */ 84const filterData = audioBuffer => { 85 const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data 86 const samples = 500; // Number of samples we want to have in our final data set 87 const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision 88 const filteredData = []; 89 for (let i = 0; i < samples; i++) { 90 let blockStart = blockSize * i; // the location of the first sample in the block 91 let sum = 0; 92 for (let j = 0; j < blockSize; j++) { 93 sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block 94 } 95 filteredData.push(sum / blockSize); // divide the sum by the block size to get the average 96 } 97 return filteredData; 98}; 99 100/** 101 * Normalizes the audio data to make a cleaner illustration 102 * @param {Array} filteredData the data from filterData() 103 * @returns {Array} an normalized array of floating point numbers 104 */ 105const normalizeData = filteredData => { 106 const multiplier = Math.pow(Math.max(...filteredData), -1); 107 let normalized = filteredData.map(n => n * multiplier); 108 return normalized; 109} 110 111 112 113/** 114 * Draws the audio file into a canvas element. 115 * @param {Array} normalizedData The filtered array returned from filterData() 116 * @returns {Array} a normalized array of data 117 */ 118const draw = ( url, normalizedData, storeData) => { 119 const canvas = jQuery.qtmplayerCanvas[0];//document.querySelector("canvas"); 120 121 const dpr = window.devicePixelRatio || 1; 122 const gap = 1; 123 const padding = 2; 124 canvas.width = canvas.offsetWidth * dpr; 125 canvas.height = (canvas.offsetHeight + padding * 2) * dpr; 126 const ctx = canvas.getContext("2d"); 127 ctx.scale(dpr, -dpr); 128 ctx.translate(0, - canvas.offsetHeight - padding- padding ); // set Y = 0 to be in the middle of the canvas 129 130 // draw the line segments
131 const width = Math.round( canvas.offsetWidth / normalizedData.length ); 132 jQuery.storedPeaks[url] = normalizedData;// Save the peaks associated with the URL to avoid reloading the same file again 133 134 qtWaveformDebug('Store data:'+storeData) 135 if(true == storeData){ 136 137 jQuery.ajax({ 138 type: "post", 139 url: ajax_var.url, 140 data: { 141 'action': 'qtmplayer-store-peaks', 142 'nonce': ajax_var.nonce, 143 'url': url, 144 'peaks': normalizedData 145 }, 146 success: function( response ){ 147 qtWaveformDebug('Data stored: '+response); 148 return true; 149 }, 150 fail: function(e){ 151 qtWaveformDebug('Data not stored: '+response); 152 return false; 153 } 154 }); 155 } 156 157 const peakSpacing = 4; 158 ctx.beginPath(); 159 ctx.moveTo( 0, 0 ); 160 let x; 161 // Draw the upper path. 162 for ( let i = 0; i < normalizedData.length; i++ ) { 163 let x = Math.round(width * i); 164 let height = normalizedData[i] * canvas.offsetHeight - padding; 165 ctx.moveTo( x, 0 ); 166 ctx.lineTo( x, height ); 167 ctx.lineTo( Math.round(width - gap + x) , height); 168 ctx.lineTo( Math.round(width - gap + x), 0); 169 ctx.lineTo( width + x , 0); 170 } 171 172 // Original full 173 ctx.moveTo( 0, 0 ); 174 ctx.strokeStyle = '#FFFFFF'; 175 ctx.fillStyle="#FFFFFF"; 176 ctx.fill(); 177 178 179 // =============== 180 // The clone 181 // =============== 182 const canvasClone = jQuery.qtmplayerCanvasClone[0];//document.querySelector("canvas"); 183 canvasClone.width = canvasClone.offsetWidth * dpr; 184 canvasClone.height = (canvasClone.offsetHeight + padding * 2) * dpr; 185 const ctxClone = canvasClone.getContext("2d"); 186 ctxClone.scale(dpr, -dpr); 187 ctxClone.translate(0, - canvasClone.offsetHeight - padding - padding ); // set Y = 0 to be in the middle of the canvas 188 ctxClone.beginPath(); 189 ctxClone.moveTo( 0, 0 ); 190 // Draw the upper path. 191 for ( let i = 0; i < normalizedData.length; i++ ) { 192 let x = width * i; 193 let height = normalizedData[i] * canvas.offsetHeight - padding; 194 ctxClone.moveTo( x, 0 ); 195 ctxClone.lineTo( x, height ); 196 ctxClone.lineTo( Math.round(width - gap + x) , height); 197 ctxClone.lineTo( Math.round(width - gap + x), 0); 198 ctxClone.lineTo( width + x , 0); 199 } 200 // Clone for tracking 201 ctxClone.moveTo( 0, 0 ); 202 ctxClone.strokeStyle = jQuery.qtmplayerCanvasCloneColor; 203 ctxClone.fillStyle=jQuery.qtmplayerCanvasCloneColor; 204 ctxClone.fill(); 205 206 207};
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.