source: gs3-extensions/mars-src/trunk/src/wavesurfer-renderer/drawer.spectrummulticanvas.js@ 34668

Last change on this file since 34668 was 34668, checked in by davidb, 3 years ago

Files to wavesurfer/src that need to be tweaked/augemented to

File size: 31.4 KB
Line 
1import Drawer from './drawer';
2import * as util from './util';
3import CanvasEntry from './drawer.canvasentry';
4
5/**
6 * Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
7 */
8/* eslint-disable complexity, no-redeclare, no-var, one-var */
9const FFT = function(bufferSize, sampleRate, windowFunc, alpha) {
10 this.bufferSize = bufferSize;
11 this.sampleRate = sampleRate;
12 this.bandwidth = (2 / bufferSize) * (sampleRate / 2);
13
14 this.sinTable = new Float32Array(bufferSize);
15 this.cosTable = new Float32Array(bufferSize);
16 this.windowValues = new Float32Array(bufferSize);
17 this.reverseTable = new Uint32Array(bufferSize);
18
19 this.peakBand = 0;
20 this.peak = 0;
21
22 var i;
23 switch (windowFunc) {
24 case 'bartlett':
25 for (i = 0; i < bufferSize; i++) {
26 this.windowValues[i] =
27 (2 / (bufferSize - 1)) *
28 ((bufferSize - 1) / 2 - Math.abs(i - (bufferSize - 1) / 2));
29 }
30 break;
31 case 'bartlettHann':
32 for (i = 0; i < bufferSize; i++) {
33 this.windowValues[i] =
34 0.62 -
35 0.48 * Math.abs(i / (bufferSize - 1) - 0.5) -
36 0.38 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
37 }
38 break;
39 case 'blackman':
40 alpha = alpha || 0.16;
41 for (i = 0; i < bufferSize; i++) {
42 this.windowValues[i] =
43 (1 - alpha) / 2 -
44 0.5 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1)) +
45 (alpha / 2) *
46 Math.cos((4 * Math.PI * i) / (bufferSize - 1));
47 }
48 break;
49 case 'cosine':
50 for (i = 0; i < bufferSize; i++) {
51 this.windowValues[i] = Math.cos(
52 (Math.PI * i) / (bufferSize - 1) - Math.PI / 2
53 );
54 }
55 break;
56 case 'gauss':
57 alpha = alpha || 0.25;
58 for (i = 0; i < bufferSize; i++) {
59 this.windowValues[i] = Math.pow(
60 Math.E,
61 -0.5 *
62 Math.pow(
63 (i - (bufferSize - 1) / 2) /
64 ((alpha * (bufferSize - 1)) / 2),
65 2
66 )
67 );
68 }
69 break;
70 case 'hamming':
71 for (i = 0; i < bufferSize; i++) {
72 this.windowValues[i] =
73 0.54 -
74 0.46 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
75 }
76 break;
77 case 'hann':
78 case undefined:
79 for (i = 0; i < bufferSize; i++) {
80 this.windowValues[i] =
81 0.5 * (1 - Math.cos((Math.PI * 2 * i) / (bufferSize - 1)));
82 }
83 break;
84 case 'lanczoz':
85 for (i = 0; i < bufferSize; i++) {
86 this.windowValues[i] =
87 Math.sin(Math.PI * ((2 * i) / (bufferSize - 1) - 1)) /
88 (Math.PI * ((2 * i) / (bufferSize - 1) - 1));
89 }
90 break;
91 case 'rectangular':
92 for (i = 0; i < bufferSize; i++) {
93 this.windowValues[i] = 1;
94 }
95 break;
96 case 'triangular':
97 for (i = 0; i < bufferSize; i++) {
98 this.windowValues[i] =
99 (2 / bufferSize) *
100 (bufferSize / 2 - Math.abs(i - (bufferSize - 1) / 2));
101 }
102 break;
103 default:
104 throw Error("No such window function '" + windowFunc + "'");
105 }
106
107 var limit = 1;
108 var bit = bufferSize >> 1;
109 var i;
110
111 while (limit < bufferSize) {
112 for (i = 0; i < limit; i++) {
113 this.reverseTable[i + limit] = this.reverseTable[i] + bit;
114 }
115
116 limit = limit << 1;
117 bit = bit >> 1;
118 }
119
120 for (i = 0; i < bufferSize; i++) {
121 this.sinTable[i] = Math.sin(-Math.PI / i);
122 this.cosTable[i] = Math.cos(-Math.PI / i);
123 }
124
125 this.calculateSpectrum = function(buffer) {
126 // Locally scope variables for speed up
127 var bufferSize = this.bufferSize,
128 cosTable = this.cosTable,
129 sinTable = this.sinTable,
130 reverseTable = this.reverseTable,
131 real = new Float32Array(bufferSize),
132 imag = new Float32Array(bufferSize),
133 bSi = 2 / this.bufferSize,
134 sqrt = Math.sqrt,
135 rval,
136 ival,
137 mag,
138 spectrum = new Float32Array(bufferSize / 2);
139
140 var k = Math.floor(Math.log(bufferSize) / Math.LN2);
141
142 if (Math.pow(2, k) !== bufferSize) {
143 throw 'Invalid buffer size, must be a power of 2.';
144 }
145 if (bufferSize !== buffer.length) {
146 throw 'Supplied buffer is not the same size as defined FFT. FFT Size: ' +
147 bufferSize +
148 ' Buffer Size: ' +
149 buffer.length;
150 }
151
152 var halfSize = 1,
153 phaseShiftStepReal,
154 phaseShiftStepImag,
155 currentPhaseShiftReal,
156 currentPhaseShiftImag,
157 off,
158 tr,
159 ti,
160 tmpReal;
161
162 for (var i = 0; i < bufferSize; i++) {
163 real[i] =
164 buffer[reverseTable[i]] * this.windowValues[reverseTable[i]];
165 imag[i] = 0;
166 }
167
168 while (halfSize < bufferSize) {
169 phaseShiftStepReal = cosTable[halfSize];
170 phaseShiftStepImag = sinTable[halfSize];
171
172 currentPhaseShiftReal = 1;
173 currentPhaseShiftImag = 0;
174
175 for (var fftStep = 0; fftStep < halfSize; fftStep++) {
176 var i = fftStep;
177
178 while (i < bufferSize) {
179 off = i + halfSize;
180 tr =
181 currentPhaseShiftReal * real[off] -
182 currentPhaseShiftImag * imag[off];
183 ti =
184 currentPhaseShiftReal * imag[off] +
185 currentPhaseShiftImag * real[off];
186
187 real[off] = real[i] - tr;
188 imag[off] = imag[i] - ti;
189 real[i] += tr;
190 imag[i] += ti;
191
192 i += halfSize << 1;
193 }
194
195 tmpReal = currentPhaseShiftReal;
196 currentPhaseShiftReal =
197 tmpReal * phaseShiftStepReal -
198 currentPhaseShiftImag * phaseShiftStepImag;
199 currentPhaseShiftImag =
200 tmpReal * phaseShiftStepImag +
201 currentPhaseShiftImag * phaseShiftStepReal;
202 }
203
204 halfSize = halfSize << 1;
205 }
206
207 for (var i = 0, N = bufferSize / 2; i < N; i++) {
208 rval = real[i];
209 ival = imag[i];
210 mag = bSi * sqrt(rval * rval + ival * ival);
211
212 if (mag > this.peak) {
213 this.peakBand = i;
214 this.peak = mag;
215 }
216 spectrum[i] = mag;
217 }
218 return spectrum;
219 };
220};
221/* eslint-enable complexity, no-redeclare, no-var, one-var */
222
223
224/**
225 * Experimental SpectrumMultiCanvas renderer for wavesurfer.
226 *
227 * A `SpectrumMultiCanvas` consists of one or more `CanvasEntry` instances, depending
228 * on the zoom level.
229 */
230export default class SpectrumMultiCanvas extends Drawer {
231 /**
232 * @param {HTMLElement} container The container node of the wavesurfer instance
233 * @param {WavesurferParams} params The wavesurfer initialisation options
234 */
235 constructor(container, params) {
236 super(container, params);
237
238 /**
239 * @type {number}
240 */
241 this.maxCanvasWidth = params.maxCanvasWidth;
242
243 /**
244 * @type {number}
245 */
246 this.maxCanvasElementWidth = Math.round(
247 params.maxCanvasWidth / params.pixelRatio
248 );
249
250 /**
251 * Whether or not the progress wave is rendered. If the `waveColor`
252 * and `progressColor` are the same color it is not.
253 *
254 * @type {boolean}
255 */
256 this.hasProgressCanvas = params.waveColor != params.progressColor;
257
258 /**
259 * @type {number}
260 */
261 this.halfPixel = 0.5 / params.pixelRatio;
262
263 /**
264 * List of `CanvasEntry` instances.
265 *
266 * @type {Array}
267 */
268 this.canvases = [];
269
270 /**
271 * @type {HTMLElement}
272 */
273 this.progressWave = null;
274
275 /**
276 * Class used to generate entries.
277 *
278 * @type {function}
279 */
280 this.EntryClass = CanvasEntry;
281
282 /**
283 * Canvas 2d context attributes.
284 *
285 * @type {object}
286 */
287 this.canvasContextAttributes = params.drawingContextAttributes;
288
289 /**
290 * Overlap added between entries to prevent vertical white stripes
291 * between `canvas` elements.
292 *
293 * @type {number}
294 */
295 this.overlap = 2 * Math.ceil(params.pixelRatio / 2);
296
297 /**
298 * The radius of the wave bars. Makes bars rounded
299 *
300 * @type {number}
301 */
302 this.barRadius = params.barRadius || 0;
303
304
305 if (params.colorMap) {
306 if (params.colorMap.length < 256) {
307 throw new Error('Colormap must contain 256 elements');
308 }
309 for (let i = 0; i < params.colorMap.length; i++) {
310 const cmEntry = params.colorMap[i];
311 if ((cmEntry.length !== 4) && (cmEntry.length !== 3)) {
312 throw new Error(
313 'ColorMap entries must contain 3 (rgb) or 4 (rgba) values'
314 );
315 }
316 }
317 this.colorMap = params.colorMap;
318 } else {
319 this.colorMap = [];
320 for (let i = 0; i < 256; i++) {
321 const val = (255 - i) / 256;
322 this.colorMap.push([val, val, val, 1]);
323 }
324 }
325
326 }
327
328 /**
329 * Initialize the drawer
330 */
331 init() {
332 this.createWrapper();
333 this.createElements();
334 }
335
336 /**
337 * Create the canvas elements and style them
338 *
339 */
340 createElements() {
341 this.progressWave = this.wrapper.appendChild(
342 this.style(document.createElement('wave'), {
343 position: 'absolute',
344 zIndex: 3,
345 left: 0,
346 top: 0,
347 bottom: 0,
348 overflow: 'hidden',
349 width: '0',
350 display: 'none',
351 boxSizing: 'border-box',
352 borderRightStyle: 'solid',
353 pointerEvents: 'none'
354 })
355 );
356
357 this.addCanvas();
358 this.updateCursor();
359 }
360
361 /**
362 * Update cursor style
363 */
364 updateCursor() {
365 this.style(this.progressWave, {
366 borderRightWidth: this.params.cursorWidth + 'px',
367 borderRightColor: this.params.cursorColor
368 });
369 }
370
371 /**
372 * Adjust to the updated size by adding or removing canvases
373 */
374 updateSize() {
375 const totalWidth = Math.round(this.width / this.params.pixelRatio);
376 const requiredCanvases = Math.ceil(
377 totalWidth / (this.maxCanvasElementWidth + this.overlap)
378 );
379
380 // add required canvases
381 while (this.canvases.length < requiredCanvases) {
382 this.addCanvas();
383 }
384
385 // remove older existing canvases, if any
386 while (this.canvases.length > requiredCanvases) {
387 this.removeCanvas();
388 }
389
390 let canvasWidth = this.maxCanvasWidth + this.overlap;
391 const lastCanvas = this.canvases.length - 1;
392 this.canvases.forEach((entry, i) => {
393 if (i == lastCanvas) {
394 canvasWidth = this.width - this.maxCanvasWidth * lastCanvas;
395 }
396 this.updateDimensions(entry, canvasWidth, this.height);
397
398 entry.clearWave();
399 });
400 }
401
402 /**
403 * Add a canvas to the canvas list
404 *
405 */
406 addCanvas() {
407 const entry = new this.EntryClass();
408 entry.canvasContextAttributes = this.canvasContextAttributes;
409 entry.hasProgressCanvas = this.hasProgressCanvas;
410 entry.halfPixel = this.halfPixel;
411 const leftOffset = this.maxCanvasElementWidth * this.canvases.length;
412
413 // wave
414 entry.initWave(
415 this.wrapper.appendChild(
416 this.style(document.createElement('canvas'), {
417 position: 'absolute',
418 zIndex: 2,
419 left: leftOffset + 'px',
420 top: 0,
421 bottom: 0,
422 height: '100%',
423 pointerEvents: 'none'
424 })
425 )
426 );
427
428 // progress
429 if (this.hasProgressCanvas) {
430 entry.initProgress(
431 this.progressWave.appendChild(
432 this.style(document.createElement('canvas'), {
433 position: 'absolute',
434 left: leftOffset + 'px',
435 top: 0,
436 bottom: 0,
437 height: '100%'
438 })
439 )
440 );
441 }
442
443 this.canvases.push(entry);
444 }
445
446 /**
447 * Pop single canvas from the list
448 *
449 */
450 removeCanvas() {
451 let lastEntry = this.canvases[this.canvases.length - 1];
452
453 // wave
454 lastEntry.wave.parentElement.removeChild(lastEntry.wave);
455
456 // progress
457 if (this.hasProgressCanvas) {
458 lastEntry.progress.parentElement.removeChild(lastEntry.progress);
459 }
460
461 // cleanup
462 if (lastEntry) {
463 lastEntry.destroy();
464 lastEntry = null;
465 }
466
467 this.canvases.pop();
468 }
469
470 /**
471 * Update the dimensions of a canvas element
472 *
473 * @param {CanvasEntry} entry Target entry
474 * @param {number} width The new width of the element
475 * @param {number} height The new height of the element
476 */
477 updateDimensions(entry, width, height) {
478 const elementWidth = Math.round(width / this.params.pixelRatio);
479 const totalWidth = Math.round(this.width / this.params.pixelRatio);
480
481 // update canvas dimensions
482 entry.updateDimensions(elementWidth, totalWidth, width, height);
483
484 // style element
485 this.style(this.progressWave, { display: 'block' });
486 }
487
488 /**
489 * Clear the whole multi-canvas
490 */
491 clearWave() {
492 util.frame(() => {
493 this.canvases.forEach(entry => entry.clearWave());
494 })();
495 }
496
497
498 drawPeaks(peaks, length, start, end) {
499 if (!this.setWidth(length)) {
500 this.clearWave();
501 }
502
503 this.getFrequencies(this.drawSpectrogram);
504 }
505
506 resample(oldMatrix) {
507 const columnsNumber = this.width;
508 const newMatrix = [];
509
510 const oldPiece = 1 / oldMatrix.length;
511 const newPiece = 1 / columnsNumber;
512 let i;
513
514 for (i = 0; i < columnsNumber; i++) {
515 const column = new Array(oldMatrix[0].length);
516 let j;
517
518 for (j = 0; j < oldMatrix.length; j++) {
519 const oldStart = j * oldPiece;
520 const oldEnd = oldStart + oldPiece;
521 const newStart = i * newPiece;
522 const newEnd = newStart + newPiece;
523
524 const overlap =
525 oldEnd <= newStart || newEnd <= oldStart
526 ? 0
527 : Math.min(
528 Math.max(oldEnd, newStart),
529 Math.max(newEnd, oldStart)
530 ) -
531 Math.max(
532 Math.min(oldEnd, newStart),
533 Math.min(newEnd, oldStart)
534 );
535 let k;
536 /* eslint-disable max-depth */
537 if (overlap > 0) {
538 for (k = 0; k < oldMatrix[0].length; k++) {
539 if (column[k] == null) {
540 column[k] = 0;
541 }
542 column[k] += (overlap / newPiece) * oldMatrix[j][k];
543 }
544 }
545 /* eslint-enable max-depth */
546 }
547
548 const intColumn = new Uint8Array(oldMatrix[0].length);
549 let m;
550
551 for (m = 0; m < oldMatrix[0].length; m++) {
552 intColumn[m] = column[m];
553 }
554
555 newMatrix.push(intColumn);
556 }
557
558 return newMatrix;
559 }
560
561 drawSpectrogram(frequenciesData, my) {
562 console.log("*** spectrummultican as::drawSpectrum() spec ....spec.length = " + frequenciesData.length)
563 console.log(frequenciesData);
564 //const spectrCc = my.spectrCc;
565 //const length = my.wavesurfer.backend.getDuration();
566 //const height = my.height;
567 //const pixels = my.resample(frequenciesData);
568 //const heightFactor = my.buffer ? 2 / my.buffer.numberOfChannels : 1;
569
570 const canvas = my.canvases[0].wave;
571 const spectrCc = canvas.getContext('2d');
572 const length = my.wavesurfer.backend.getDuration();
573 const height = canvas.height;
574 const pixels = my.resample(frequenciesData);
575 const heightFactor = my.buffer ? 2 / my.buffer.numberOfChannels : 1;
576
577
578 let i;
579 let j;
580
581 for (i = 0; i < pixels.length; i++) {
582 for (j = 0; j < pixels[i].length; j++) {
583 const colorMap = my.colorMap[pixels[i][j]]; // ****
584 if (colorMap.length == 4) {
585 //my.spectrCc.fillStyle =
586 spectrCc.fillStyle =
587 'rgba(' +
588 colorMap[0] * 256 +
589 ', ' +
590 colorMap[1] * 256 +
591 ', ' +
592 colorMap[2] * 256 +
593 ',' +
594 colorMap[3] +
595 ')';
596 }
597 else {
598 //my.spectrCc.fillStyle =
599 spectrCc.fillStyle =
600 'rgb(' +
601 colorMap[0] * 256 +
602 ', ' +
603 colorMap[1] * 256 +
604 ', ' +
605 colorMap[2] * 256 +
606 ')';
607 }
608
609 //my.spectrCc.fillRect(
610 spectrCc.fillRect(
611 i,
612 height - j * heightFactor,
613 1,
614 heightFactor
615 );
616 }
617 }
618 }
619
620 getFrequencies(callback) {
621 const fftSamples = this.fftSamples || 512;
622 const buffer = (this.buffer = this.wavesurfer.backend.buffer);
623 if (buffer == null) return; // **** GS3 edit
624 const channelOne = buffer.getChannelData(0);
625 const bufferLength = buffer.length;
626 const sampleRate = buffer.sampleRate;
627 const frequencies = [];
628
629 if (!buffer) {
630 this.fireEvent('error', 'Web Audio buffer is not available');
631 return;
632 }
633
634 let noverlap = this.noverlap;
635 if (!noverlap) {
636 const uniqueSamplesPerPx = buffer.length / this.canvases[0].wave.width; // **** GS3 edit
637 noverlap = Math.max(0, Math.round(fftSamples - uniqueSamplesPerPx));
638 }
639
640 const fft = new FFT(
641 fftSamples,
642 sampleRate,
643 this.windowFunc,
644 this.alpha
645 );
646 const maxSlicesCount = Math.floor(
647 bufferLength / (fftSamples - noverlap)
648 );
649 let currentOffset = 0;
650
651 while (currentOffset + fftSamples < channelOne.length) {
652 const segment = channelOne.slice(
653 currentOffset,
654 currentOffset + fftSamples
655 );
656 const spectrum = fft.calculateSpectrum(segment);
657 const array = new Uint8Array(fftSamples / 2);
658 let j;
659 for (j = 0; j < fftSamples / 2; j++) {
660 array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
661 }
662 frequencies.push(array);
663 currentOffset += fftSamples - noverlap;
664 }
665 callback(frequencies, this);
666 }
667
668
669 /**
670 * Draw a waveform with bars
671 *
672 * @param {number[]|Number.<Array[]>} peaks Can also be an array of arrays
673 * for split channel rendering
674 * @param {number} channelIndex The index of the current channel. Normally
675 * should be 0. Must be an integer.
676 * @param {number} start The x-offset of the beginning of the area that
677 * should be rendered
678 * @param {number} end The x-offset of the end of the area that should be
679 * rendered
680 * @returns {void}
681 */
682 drawBars(peaks, channelIndex, start, end) {
683 return this.prepareDraw(
684 peaks,
685 channelIndex,
686 start,
687 end,
688 ({ absmax, hasMinVals, height, offsetY, halfH, peaks }) => {
689 // if drawBars was called within ws.empty we don't pass a start and
690 // don't want anything to happen
691 if (start === undefined) {
692 return;
693 }
694 // Skip every other value if there are negatives.
695 const peakIndexScale = hasMinVals ? 2 : 1;
696 const length = peaks.length / peakIndexScale;
697 const bar = this.params.barWidth * this.params.pixelRatio;
698 const gap =
699 this.params.barGap === null
700 ? Math.max(this.params.pixelRatio, ~~(bar / 2))
701 : Math.max(
702 this.params.pixelRatio,
703 this.params.barGap * this.params.pixelRatio
704 );
705 const step = bar + gap;
706
707 const scale = length / this.width;
708 const first = start;
709 const last = end;
710 let i = first;
711
712 for (i; i < last; i += step) {
713 const peak =
714 peaks[Math.floor(i * scale * peakIndexScale)] || 0;
715 let h = Math.round((peak / absmax) * halfH);
716
717 /* in case of silences, allow the user to specify that we
718 * always draw *something* (normally a 1px high bar) */
719 if (h == 0 && this.params.barMinHeight)
720 h = this.params.barMinHeight;
721
722 this.fillRect(
723 i + this.halfPixel,
724 halfH - h + offsetY,
725 bar + this.halfPixel,
726 h * 2,
727 this.barRadius
728 );
729 }
730 }
731 );
732 }
733
734 /**
735 * Draw a waveform
736 *
737 * @param {number[]|Number.<Array[]>} peaks Can also be an array of arrays
738 * for split channel rendering
739 * @param {number} channelIndex The index of the current channel. Normally
740 * should be 0
741 * @param {number?} start The x-offset of the beginning of the area that
742 * should be rendered (If this isn't set only a flat line is rendered)
743 * @param {number?} end The x-offset of the end of the area that should be
744 * rendered
745 * @returns {void}
746 */
747 drawWave(peaks, channelIndex, start, end) {
748 return this.prepareDraw(
749 peaks,
750 channelIndex,
751 start,
752 end,
753 ({ absmax, hasMinVals, height, offsetY, halfH, peaks, channelIndex }) => {
754 if (!hasMinVals) {
755 const reflectedPeaks = [];
756 const len = peaks.length;
757 let i = 0;
758 for (i; i < len; i++) {
759 reflectedPeaks[2 * i] = peaks[i];
760 reflectedPeaks[2 * i + 1] = -peaks[i];
761 }
762 peaks = reflectedPeaks;
763 }
764
765 // if drawWave was called within ws.empty we don't pass a start and
766 // end and simply want a flat line
767 if (start !== undefined) {
768 this.drawLine(peaks, absmax, halfH, offsetY, start, end, channelIndex);
769 }
770
771 // always draw a median line
772 this.fillRect(
773 0,
774 halfH + offsetY - this.halfPixel,
775 this.width,
776 this.halfPixel,
777 this.barRadius
778 );
779 }
780 );
781 }
782
783 /**
784 * Tell the canvas entries to render their portion of the waveform
785 *
786 * @param {number[]} peaks Peaks data
787 * @param {number} absmax Maximum peak value (absolute)
788 * @param {number} halfH Half the height of the waveform
789 * @param {number} offsetY Offset to the top
790 * @param {number} start The x-offset of the beginning of the area that
791 * should be rendered
792 * @param {number} end The x-offset of the end of the area that
793 * should be rendered
794 * @param {channelIndex} channelIndex The channel index of the line drawn
795 */
796 drawLine(peaks, absmax, halfH, offsetY, start, end, channelIndex) {
797 const { waveColor, progressColor } = this.params.splitChannelsOptions.channelColors[channelIndex] || {};
798 this.canvases.forEach((entry, i) => {
799 this.setFillStyles(entry, waveColor, progressColor);
800 entry.drawLines(peaks, absmax, halfH, offsetY, start, end);
801 });
802 }
803
804 /**
805 * Draw a rectangle on the multi-canvas
806 *
807 * @param {number} x X-position of the rectangle
808 * @param {number} y Y-position of the rectangle
809 * @param {number} width Width of the rectangle
810 * @param {number} height Height of the rectangle
811 * @param {number} radius Radius of the rectangle
812 */
813 fillRect(x, y, width, height, radius) {
814 const startCanvas = Math.floor(x / this.maxCanvasWidth);
815 const endCanvas = Math.min(
816 Math.ceil((x + width) / this.maxCanvasWidth) + 1,
817 this.canvases.length
818 );
819 let i = startCanvas;
820 for (i; i < endCanvas; i++) {
821 const entry = this.canvases[i];
822 const leftOffset = i * this.maxCanvasWidth;
823
824 const intersection = {
825 x1: Math.max(x, i * this.maxCanvasWidth),
826 y1: y,
827 x2: Math.min(
828 x + width,
829 i * this.maxCanvasWidth + entry.wave.width
830 ),
831 y2: y + height
832 };
833
834 if (intersection.x1 < intersection.x2) {
835 this.setFillStyles(entry);
836
837 entry.fillRects(
838 intersection.x1 - leftOffset,
839 intersection.y1,
840 intersection.x2 - intersection.x1,
841 intersection.y2 - intersection.y1,
842 radius
843 );
844 }
845 }
846 }
847
848 /**
849 * Returns whether to hide the channel from being drawn based on params.
850 *
851 * @param {number} channelIndex The index of the current channel.
852 * @returns {bool} True to hide the channel, false to draw.
853 */
854 hideChannel(channelIndex) {
855 return this.params.splitChannels && this.params.splitChannelsOptions.filterChannels.includes(channelIndex);
856 }
857
858 /**
859 * Performs preparation tasks and calculations which are shared by `drawBars`
860 * and `drawWave`
861 *
862 * @param {number[]|Number.<Array[]>} peaks Can also be an array of arrays for
863 * split channel rendering
864 * @param {number} channelIndex The index of the current channel. Normally
865 * should be 0
866 * @param {number?} start The x-offset of the beginning of the area that
867 * should be rendered. If this isn't set only a flat line is rendered
868 * @param {number?} end The x-offset of the end of the area that should be
869 * rendered
870 * @param {function} fn The render function to call, e.g. `drawWave`
871 * @param {number} drawIndex The index of the current channel after filtering.
872 * @returns {void}
873 */
874 prepareDraw(peaks, channelIndex, start, end, fn, drawIndex) {
875 return util.frame(() => {
876 // Split channels and call this function with the channelIndex set
877 if (peaks[0] instanceof Array) {
878 const channels = peaks;
879
880 if (this.params.splitChannels) {
881 const filteredChannels = channels.filter((c, i) => !this.hideChannel(i));
882 if (!this.params.splitChannelsOptions.overlay) {
883 this.setHeight(
884 Math.max(filteredChannels.length, 1) *
885 this.params.height *
886 this.params.pixelRatio
887 );
888 }
889
890 return channels.forEach((channelPeaks, i) =>
891 this.prepareDraw(channelPeaks, i, start, end, fn, filteredChannels.indexOf(channelPeaks))
892 );
893 }
894 peaks = channels[0];
895 }
896
897 // Return and do not draw channel peaks if hidden.
898 if (this.hideChannel(channelIndex)) {
899 return;
900 }
901
902 // calculate maximum modulation value, either from the barHeight
903 // parameter or if normalize=true from the largest value in the peak
904 // set
905 let absmax = 1 / this.params.barHeight;
906 if (this.params.normalize) {
907 const max = util.max(peaks);
908 const min = util.min(peaks);
909 absmax = -min > max ? -min : max;
910 }
911
912 // Bar wave draws the bottom only as a reflection of the top,
913 // so we don't need negative values
914 const hasMinVals = [].some.call(peaks, val => val < 0);
915 const height = this.params.height * this.params.pixelRatio;
916 const offsetY = height * drawIndex || 0;
917 const halfH = height / 2;
918
919 return fn({
920 absmax: absmax,
921 hasMinVals: hasMinVals,
922 height: height,
923 offsetY: offsetY,
924 halfH: halfH,
925 peaks: peaks,
926 channelIndex: channelIndex,
927 });
928 })();
929 }
930
931 /**
932 * Set the fill styles for a certain entry (wave and progress)
933 *
934 * @param {CanvasEntry} entry Target entry
935 * @param {string} waveColor Wave color to draw this entry
936 * @param {string} progressColor Progress color to draw this entry
937 */
938 setFillStyles(entry, waveColor = this.params.waveColor, progressColor = this.params.progressColor) {
939 entry.setFillStyles(waveColor, progressColor);
940 }
941
942 /**
943 * Return image data of the multi-canvas
944 *
945 * When using a `type` of `'blob'`, this will return a `Promise`.
946 *
947 * @param {string} format='image/png' An optional value of a format type.
948 * @param {number} quality=0.92 An optional value between 0 and 1.
949 * @param {string} type='dataURL' Either 'dataURL' or 'blob'.
950 * @return {string|string[]|Promise} When using the default `'dataURL'`
951 * `type` this returns a single data URL or an array of data URLs,
952 * one for each canvas. When using the `'blob'` `type` this returns a
953 * `Promise` that resolves with an array of `Blob` instances, one for each
954 * canvas.
955 */
956 getImage(format, quality, type) {
957 if (type === 'blob') {
958 return Promise.all(
959 this.canvases.map(entry => {
960 return entry.getImage(format, quality, type);
961 })
962 );
963 } else if (type === 'dataURL') {
964 let images = this.canvases.map(entry =>
965 entry.getImage(format, quality, type)
966 );
967 return images.length > 1 ? images : images[0];
968 }
969 }
970
971 /**
972 * Render the new progress
973 *
974 * @param {number} position X-offset of progress position in pixels
975 */
976 updateProgress(position) {
977 this.style(this.progressWave, { width: position + 'px' });
978 }
979}
Note: See TracBrowser for help on using the repository browser.