wavesurfer.js 7.0.0-alpha.43 → 7.0.0-alpha.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,165 +1,337 @@
1
+ /**
2
+ * Spectrogram plugin
3
+ *
4
+ * Render a spectrogram visualisation of the audio.
5
+ *
6
+ * @example
7
+ * // ... initialising wavesurfer with the plugin
8
+ * var wavesurfer = WaveSurfer.create({
9
+ * // wavesurfer options ...
10
+ * plugins: [
11
+ * SpectrogramPlugin.create({
12
+ * // plugin options ...
13
+ * })
14
+ * ]
15
+ * });
16
+ */
17
+ // @ts-nocheck
1
18
  import BasePlugin from '../base-plugin.js';
2
- class SpectrogramPlugin extends BasePlugin {
3
- mediaSpectrogramer = null;
4
- recordedUrl = '';
19
+ import FFT from './spectrogram-fft.js';
20
+ export default class SpectrogramPlugin extends BasePlugin {
5
21
  static create(options) {
6
22
  return new SpectrogramPlugin(options || {});
7
23
  }
8
- loadBlob(data) {
9
- const blob = new Blob(data, { type: 'audio/webm' });
10
- this.recordedUrl = URL.createObjectURL(blob);
11
- this.wavesurfer?.load(this.recordedUrl);
12
- }
13
- // Function to process frequency data
14
- processFrequencyData(dataArray, sampleRate) {
15
- // Smooth the dataArray using a moving average
16
- const windowSize = 5; // Adjust the window size as needed
17
- const smoothedDataArray = new Float32Array(dataArray.length);
18
- for (let i = 0; i < dataArray.length; i++) {
19
- let sum = 0;
20
- let count = 0;
21
- for (let j = i - Math.floor(windowSize / 2); j <= i + Math.floor(windowSize / 2); j++) {
22
- if (j >= 0 && j < dataArray.length) {
23
- sum += dataArray[j];
24
- count++;
25
- }
26
- }
27
- smoothedDataArray[i] = sum / count;
28
- }
29
- // Find the first two peaks (formants) above a certain threshold
30
- const threshold = -70; // Adjust the threshold as needed
31
- const formants = [];
32
- let prevValue = smoothedDataArray[0];
33
- let prevSlope = 0;
34
- for (let i = 1; i < smoothedDataArray.length - 1; i++) {
35
- const slope = smoothedDataArray[i] - prevValue;
36
- if (slope > 0 && prevSlope < 0 && smoothedDataArray[i] > threshold) {
37
- formants.push(i);
38
- if (formants.length >= 2) {
39
- break;
40
- }
24
+ constructor(options) {
25
+ super(options);
26
+ this.utils = {
27
+ style: (el, styles) => Object.assign(el.style, styles),
28
+ };
29
+ this.drawSpectrogram = (frequenciesData) => {
30
+ if (!isNaN(frequenciesData[0][0])) {
31
+ // data is 1ch [sample, freq] format
32
+ // to [channel, sample, freq] format
33
+ frequenciesData = [frequenciesData];
41
34
  }
42
- prevValue = smoothedDataArray[i];
43
- prevSlope = slope;
44
- }
45
- // Convert the formant indices to frequencies
46
- const nyquist = 0.5 * sampleRate;
47
- const freqStep = nyquist / dataArray.length;
48
- const formantFrequencies = formants.map(index => index * freqStep);
49
- // Print the formant frequencies
50
- if (formantFrequencies.length >= 2) {
51
- return formantFrequencies;
52
- }
53
- }
54
- render(stream) {
55
- if (!this.container || !this.wavesurfer)
56
- return () => undefined;
57
- const canvas = document.createElement('canvas');
58
- canvas.width = this.container.clientWidth;
59
- canvas.height = this.container.clientHeight;
60
- canvas.style.zIndex = '10';
61
- this.container.appendChild(canvas);
62
- const canvasCtx = canvas.getContext('2d');
63
- const audioContext = new AudioContext();
64
- const source = audioContext.createMediaStreamSource(stream);
65
- const analyser = audioContext.createAnalyser();
66
- source.connect(analyser);
67
- // An array that will store real-time waveform data
68
- const bufferLength = analyser.frequencyBinCount;
69
- const dataArray = new Uint8Array(bufferLength);
70
- // An array that will store FFT data
71
- const freqArray = new Float32Array(bufferLength);
72
- let animationId;
73
- const drawWaveform = () => {
74
- if (!canvasCtx)
35
+ const spectrCc = this.spectrCc;
36
+ const height = this.height;
37
+ const width = this.width;
38
+ const freqFrom = this.buffer.sampleRate / 2;
39
+ const freqMin = this.frequencyMin;
40
+ const freqMax = this.frequencyMax;
41
+ if (!spectrCc) {
75
42
  return;
76
- canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
77
- analyser.getByteTimeDomainData(dataArray);
78
- analyser.getFloatFrequencyData(freqArray);
79
- this.processFrequencyData(freqArray, audioContext.sampleRate);
80
- canvasCtx.lineWidth = this.options.lineWidth || 2;
81
- canvasCtx.strokeStyle = this.options.waveColor || this.wavesurfer?.options.waveColor || '';
82
- canvasCtx.beginPath();
83
- const sliceWidth = (canvas.width * 1.0) / bufferLength;
84
- let x = 0;
85
- for (let i = 0; i < bufferLength; i++) {
86
- const v = dataArray[i] / 128.0;
87
- const y = (v * canvas.height) / 2;
88
- if (i === 0) {
89
- canvasCtx.moveTo(x, y);
90
- }
91
- else {
92
- canvasCtx.lineTo(x, y);
43
+ }
44
+ for (let c = 0; c < frequenciesData.length; c++) {
45
+ // for each channel
46
+ const pixels = this.resample(frequenciesData[c]);
47
+ const imageData = new ImageData(width, height);
48
+ for (let i = 0; i < pixels.length; i++) {
49
+ for (let j = 0; j < pixels[i].length; j++) {
50
+ const colorMap = this.colorMap[pixels[i][j]];
51
+ const redIndex = ((height - j) * width + i) * 4;
52
+ imageData.data[redIndex] = colorMap[0] * 255;
53
+ imageData.data[redIndex + 1] = colorMap[1] * 255;
54
+ imageData.data[redIndex + 2] = colorMap[2] * 255;
55
+ imageData.data[redIndex + 3] = colorMap[3] * 255;
56
+ }
93
57
  }
94
- x += sliceWidth;
58
+ // scale and stack spectrograms
59
+ createImageBitmap(imageData).then((renderer) => {
60
+ spectrCc.drawImage(renderer, 0, height * (1 - freqMax / freqFrom), // source x, y
61
+ width, (height * (freqMax - freqMin)) / freqFrom, // source width, height
62
+ 0, height * c, // destination x, y
63
+ width, height);
64
+ });
95
65
  }
96
- canvasCtx.lineTo(canvas.width, canvas.height / 2);
97
- canvasCtx.stroke();
98
- animationId = requestAnimationFrame(drawWaveform);
66
+ this.emit('ready');
99
67
  };
100
- drawWaveform();
101
- return () => {
102
- if (animationId) {
103
- cancelAnimationFrame(animationId);
68
+ this.frequenciesDataUrl = options.frequenciesDataUrl;
69
+ this.container =
70
+ 'string' == typeof options.container ? document.querySelector(options.container) : options.container;
71
+ if (options.colorMap) {
72
+ if (options.colorMap.length < 256) {
73
+ throw new Error('Colormap must contain 256 elements');
104
74
  }
105
- if (source) {
106
- source.disconnect();
107
- source.mediaStream.getTracks().forEach((track) => track.stop());
75
+ for (let i = 0; i < options.colorMap.length; i++) {
76
+ const cmEntry = options.colorMap[i];
77
+ if (cmEntry.length !== 4) {
78
+ throw new Error('ColorMap entries must contain 4 values');
79
+ }
108
80
  }
109
- if (audioContext) {
110
- audioContext.close();
81
+ this.colorMap = options.colorMap;
82
+ }
83
+ else {
84
+ this.colorMap = [];
85
+ for (let i = 0; i < 256; i++) {
86
+ const val = (255 - i) / 256;
87
+ this.colorMap.push([val, val, val, 1]);
111
88
  }
112
- canvas?.remove();
113
- };
114
- }
115
- cleanUp() {
116
- this.stopSpectrograming();
117
- this.wavesurfer?.empty();
118
- if (this.recordedUrl) {
119
- URL.revokeObjectURL(this.recordedUrl);
120
- this.recordedUrl = '';
121
89
  }
90
+ this.fftSamples = options.fftSamples || 512;
91
+ this.height = options.height || this.fftSamples / 2;
92
+ this.noverlap = options.noverlap;
93
+ this.windowFunc = options.windowFunc;
94
+ this.alpha = options.alpha;
95
+ this.channels = 1;
96
+ // Getting file's original samplerate is difficult(#1248).
97
+ // So set 12kHz default to render like wavesurfer.js 5.x.
98
+ this.frequencyMin = options.frequencyMin || 0;
99
+ this.frequencyMax = options.frequencyMax || 0;
100
+ this.createWrapper();
101
+ this.createCanvas();
122
102
  }
123
- async startSpectrograming() {
124
- this.cleanUp();
125
- let stream;
126
- try {
127
- stream = await navigator.mediaDevices.getUserMedia({ audio: true });
103
+ onInit() {
104
+ this.container = this.container || this.wavesurfer.getWrapper();
105
+ this.container.appendChild(this.wrapper);
106
+ if (this.wavesurfer.options.fillParent) {
107
+ this.utils.style(this.wrapper, {
108
+ width: '100%',
109
+ overflowX: 'hidden',
110
+ overflowY: 'hidden',
111
+ });
128
112
  }
129
- catch (err) {
130
- throw new Error('Error accessing the microphone: ' + err.message);
113
+ this.width = this.wavesurfer.getWrapper().offsetWidth;
114
+ this.subscriptions.push(this.wavesurfer.on('redraw', () => this.render()));
115
+ }
116
+ destroy() {
117
+ this.unAll();
118
+ this.wavesurfer.un('ready', this._onReady);
119
+ this.wavesurfer.un('redraw', this._onRender);
120
+ this.wavesurfer = null;
121
+ this.util = null;
122
+ this.options = null;
123
+ if (this.wrapper) {
124
+ this.wrapper.remove();
125
+ this.wrapper = null;
131
126
  }
132
- const onStop = this.render(stream);
133
- const mediaSpectrogramer = new MediaSpectrogramer(stream);
134
- const recordedChunks = [];
135
- mediaSpectrogramer.addEventListener('dataavailable', (event) => {
136
- if (event.data.size > 0) {
137
- recordedChunks.push(event.data);
138
- }
127
+ }
128
+ createWrapper() {
129
+ this.wrapper = document.createElement('div');
130
+ this.utils.style(this.wrapper, {
131
+ display: 'block',
132
+ position: 'relative',
133
+ userSelect: 'none',
134
+ webkitUserSelect: 'none',
135
+ height: `${this.height * this.channels}px`,
139
136
  });
140
- mediaSpectrogramer.addEventListener('stop', () => {
141
- onStop();
142
- this.loadBlob(recordedChunks);
143
- this.emit('stopSpectrograming');
137
+ // if labels are active
138
+ if (this.options.labels) {
139
+ const labelsEl = document.createElement('canvas');
140
+ labelsEl.classList.add('spec-labels');
141
+ this.utils.style(labelsEl, {
142
+ position: 'absolute',
143
+ zIndex: 9,
144
+ width: '55px',
145
+ height: '100%',
146
+ });
147
+ this.wrapper.appendChild(labelsEl);
148
+ this.labelsEl = labelsEl;
149
+ }
150
+ this.wrapper.addEventListener('click', this._onWrapperClick);
151
+ }
152
+ _wrapperClickHandler(event) {
153
+ event.preventDefault();
154
+ const relX = 'offsetX' in event ? event.offsetX : event.layerX;
155
+ this.emit('click', relX / this.width || 0);
156
+ }
157
+ createCanvas() {
158
+ const canvas = document.createElement('canvas');
159
+ this.wrapper.appendChild(canvas);
160
+ this.spectrCc = canvas.getContext('2d');
161
+ this.utils.style(canvas, {
162
+ position: 'absolute',
163
+ left: 0,
164
+ top: 0,
165
+ width: '100%',
166
+ height: '100%',
167
+ zIndex: 4,
144
168
  });
145
- mediaSpectrogramer.start();
146
- this.emit('startSpectrograming');
147
- this.mediaSpectrogramer = mediaSpectrogramer;
169
+ this.canvas = canvas;
148
170
  }
149
- isSpectrograming() {
150
- return this.mediaSpectrogramer?.state === 'recording';
171
+ render() {
172
+ this.canvas.width = this.width;
173
+ this.canvas.height = this.height;
174
+ if (this.frequenciesDataUrl) {
175
+ this.loadFrequenciesData(this.frequenciesDataUrl);
176
+ }
177
+ else {
178
+ this.getFrequencies(this.drawSpectrogram);
179
+ }
180
+ this.loadLabels('rgba(68,68,68,0.5)', '12px', '10px', '', '#fff', '#f7f7f7', 'center', '#specLabels');
151
181
  }
152
- stopSpectrograming() {
153
- if (this.isSpectrograming()) {
154
- this.mediaSpectrogramer?.stop();
182
+ getFrequencies(callback) {
183
+ const fftSamples = this.fftSamples;
184
+ const buffer = this.wavesurfer.getDecodedData();
185
+ const channels = this.channels;
186
+ this.frequencyMax = this.frequencyMax || buffer.sampleRate / 2;
187
+ if (!buffer)
188
+ return;
189
+ this.buffer = buffer;
190
+ // This may differ from file samplerate. Browser resamples audio.
191
+ const sampleRate = buffer.sampleRate;
192
+ const frequencies = [];
193
+ let noverlap = this.noverlap;
194
+ if (!noverlap) {
195
+ const uniqueSamplesPerPx = buffer.length / this.canvas.width;
196
+ noverlap = Math.max(0, Math.round(fftSamples - uniqueSamplesPerPx));
155
197
  }
198
+ const fft = new FFT(fftSamples, sampleRate, this.windowFunc, this.alpha);
199
+ for (let c = 0; c < channels; c++) {
200
+ // for each channel
201
+ const channelData = buffer.getChannelData(c);
202
+ const channelFreq = [];
203
+ let currentOffset = 0;
204
+ while (currentOffset + fftSamples < channelData.length) {
205
+ const segment = channelData.slice(currentOffset, currentOffset + fftSamples);
206
+ const spectrum = fft.calculateSpectrum(segment);
207
+ const array = new Uint8Array(fftSamples / 2);
208
+ let j;
209
+ for (j = 0; j < fftSamples / 2; j++) {
210
+ array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
211
+ }
212
+ channelFreq.push(array);
213
+ // channelFreq: [sample, freq]
214
+ currentOffset += fftSamples - noverlap;
215
+ }
216
+ frequencies.push(channelFreq);
217
+ // frequencies: [channel, sample, freq]
218
+ }
219
+ callback(frequencies, this);
156
220
  }
157
- getSpectrogramedUrl() {
158
- return this.recordedUrl;
221
+ loadFrequenciesData(url) {
222
+ return fetch(url)
223
+ .then((response) => response.json())
224
+ .then(this.drawSpectrogram);
159
225
  }
160
- destroy() {
161
- super.destroy();
162
- this.cleanUp();
226
+ freqType(freq) {
227
+ return freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq);
228
+ }
229
+ unitType(freq) {
230
+ return freq >= 1000 ? 'KHz' : 'Hz';
231
+ }
232
+ loadLabels(bgFill, fontSizeFreq, fontSizeUnit, fontType, textColorFreq, textColorUnit, textAlign, container) {
233
+ const frequenciesHeight = this.height;
234
+ bgFill = bgFill || 'rgba(68,68,68,0)';
235
+ fontSizeFreq = fontSizeFreq || '12px';
236
+ fontSizeUnit = fontSizeUnit || '10px';
237
+ fontType = fontType || 'Helvetica';
238
+ textColorFreq = textColorFreq || '#fff';
239
+ textColorUnit = textColorUnit || '#fff';
240
+ textAlign = textAlign || 'center';
241
+ container = container || '#specLabels';
242
+ const bgWidth = 55;
243
+ const getMaxY = frequenciesHeight || 512;
244
+ const labelIndex = 5 * (getMaxY / 256);
245
+ const freqStart = this.frequencyMin;
246
+ const step = (this.frequencyMax - freqStart) / labelIndex;
247
+ // prepare canvas element for labels
248
+ const ctx = this.labelsEl.getContext('2d');
249
+ const dispScale = window.devicePixelRatio;
250
+ this.labelsEl.height = this.height * this.channels * dispScale;
251
+ this.labelsEl.width = bgWidth * dispScale;
252
+ ctx.scale(dispScale, dispScale);
253
+ if (!ctx) {
254
+ return;
255
+ }
256
+ for (let c = 0; c < this.channels; c++) {
257
+ // for each channel
258
+ // fill background
259
+ ctx.fillStyle = bgFill;
260
+ ctx.fillRect(0, c * getMaxY, bgWidth, (1 + c) * getMaxY);
261
+ ctx.fill();
262
+ let i;
263
+ // render labels
264
+ for (i = 0; i <= labelIndex; i++) {
265
+ ctx.textAlign = textAlign;
266
+ ctx.textBaseline = 'middle';
267
+ const freq = freqStart + step * i;
268
+ const label = this.freqType(freq);
269
+ const units = this.unitType(freq);
270
+ const yLabelOffset = 2;
271
+ const x = 16;
272
+ let y;
273
+ if (i == 0) {
274
+ y = (1 + c) * getMaxY + i - 10;
275
+ // unit label
276
+ ctx.fillStyle = textColorUnit;
277
+ ctx.font = fontSizeUnit + ' ' + fontType;
278
+ ctx.fillText(units, x + 24, y);
279
+ // freq label
280
+ ctx.fillStyle = textColorFreq;
281
+ ctx.font = fontSizeFreq + ' ' + fontType;
282
+ ctx.fillText(label, x, y);
283
+ }
284
+ else {
285
+ y = (1 + c) * getMaxY - i * 50 + yLabelOffset;
286
+ // unit label
287
+ ctx.fillStyle = textColorUnit;
288
+ ctx.font = fontSizeUnit + ' ' + fontType;
289
+ ctx.fillText(units, x + 24, y);
290
+ // freq label
291
+ ctx.fillStyle = textColorFreq;
292
+ ctx.font = fontSizeFreq + ' ' + fontType;
293
+ ctx.fillText(label, x, y);
294
+ }
295
+ }
296
+ }
297
+ }
298
+ resample(oldMatrix) {
299
+ const columnsNumber = this.width;
300
+ const newMatrix = [];
301
+ const oldPiece = 1 / oldMatrix.length;
302
+ const newPiece = 1 / columnsNumber;
303
+ let i;
304
+ for (i = 0; i < columnsNumber; i++) {
305
+ const column = new Array(oldMatrix[0].length);
306
+ let j;
307
+ for (j = 0; j < oldMatrix.length; j++) {
308
+ const oldStart = j * oldPiece;
309
+ const oldEnd = oldStart + oldPiece;
310
+ const newStart = i * newPiece;
311
+ const newEnd = newStart + newPiece;
312
+ const overlap = oldEnd <= newStart || newEnd <= oldStart
313
+ ? 0
314
+ : Math.min(Math.max(oldEnd, newStart), Math.max(newEnd, oldStart)) -
315
+ Math.max(Math.min(oldEnd, newStart), Math.min(newEnd, oldStart));
316
+ let k;
317
+ /* eslint-disable max-depth */
318
+ if (overlap > 0) {
319
+ for (k = 0; k < oldMatrix[0].length; k++) {
320
+ if (column[k] == null) {
321
+ column[k] = 0;
322
+ }
323
+ column[k] += (overlap / newPiece) * oldMatrix[j][k];
324
+ }
325
+ }
326
+ /* eslint-enable max-depth */
327
+ }
328
+ const intColumn = new Uint8Array(oldMatrix[0].length);
329
+ let m;
330
+ for (m = 0; m < oldMatrix[0].length; m++) {
331
+ intColumn[m] = column[m];
332
+ }
333
+ newMatrix.push(intColumn);
334
+ }
335
+ return newMatrix;
163
336
  }
164
337
  }
165
- export default SpectrogramPlugin;
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Spectrogram=e():t.Spectrogram=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,s)=>{for(var i in s)t.o(s,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:s[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>r});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const s=this.on(t,e),i=this.on(t,(()=>{s(),i()}));return s}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},i=class extends s{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}};function a(t,e,s,i){switch(this.bufferSize=t,this.sampleRate=e,this.bandwidth=2/t*(e/2),this.sinTable=new Float32Array(t),this.cosTable=new Float32Array(t),this.windowValues=new Float32Array(t),this.reverseTable=new Uint32Array(t),this.peakBand=0,this.peak=0,s){case"bartlett":for(a=0;a<t;a++)this.windowValues[a]=2/(t-1)*((t-1)/2-Math.abs(a-(t-1)/2));break;case"bartlettHann":for(a=0;a<t;a++)this.windowValues[a]=.62-.48*Math.abs(a/(t-1)-.5)-.38*Math.cos(2*Math.PI*a/(t-1));break;case"blackman":for(i=i||.16,a=0;a<t;a++)this.windowValues[a]=(1-i)/2-.5*Math.cos(2*Math.PI*a/(t-1))+i/2*Math.cos(4*Math.PI*a/(t-1));break;case"cosine":for(a=0;a<t;a++)this.windowValues[a]=Math.cos(Math.PI*a/(t-1)-Math.PI/2);break;case"gauss":for(i=i||.25,a=0;a<t;a++)this.windowValues[a]=Math.pow(Math.E,-.5*Math.pow((a-(t-1)/2)/(i*(t-1)/2),2));break;case"hamming":for(a=0;a<t;a++)this.windowValues[a]=.54-.46*Math.cos(2*Math.PI*a/(t-1));break;case"hann":case void 0:for(a=0;a<t;a++)this.windowValues[a]=.5*(1-Math.cos(2*Math.PI*a/(t-1)));break;case"lanczoz":for(a=0;a<t;a++)this.windowValues[a]=Math.sin(Math.PI*(2*a/(t-1)-1))/(Math.PI*(2*a/(t-1)-1));break;case"rectangular":for(a=0;a<t;a++)this.windowValues[a]=1;break;case"triangular":for(a=0;a<t;a++)this.windowValues[a]=2/t*(t/2-Math.abs(a-(t-1)/2));break;default:throw Error("No such window function '"+s+"'")}for(var a,r=1,n=t>>1;r<t;){for(a=0;a<r;a++)this.reverseTable[a+r]=this.reverseTable[a]+n;r<<=1,n>>=1}for(a=0;a<t;a++)this.sinTable[a]=Math.sin(-Math.PI/a),this.cosTable[a]=Math.cos(-Math.PI/a);this.calculateSpectrum=function(t){var e,s,i,a=this.bufferSize,r=this.cosTable,n=this.sinTable,h=this.reverseTable,l=new Float32Array(a),o=new Float32Array(a),c=2/this.bufferSize,f=Math.sqrt,p=new Float32Array(a/2),u=Math.floor(Math.log(a)/Math.LN2);if(Math.pow(2,u)!==a)throw"Invalid buffer size, must be a power of 2.";if(a!==t.length)throw"Supplied buffer is not the same size as defined FFT. FFT Size: "+a+" Buffer Size: "+t.length;for(var d,w,M,b,g,m,y,v,x=1,S=0;S<a;S++)l[S]=t[h[S]]*this.windowValues[h[S]],o[S]=0;for(;x<a;){d=r[x],w=n[x],M=1,b=0;for(var k=0;k<x;k++){for(S=k;S<a;)m=M*l[g=S+x]-b*o[g],y=M*o[g]+b*l[g],l[g]=l[S]-m,o[g]=o[S]-y,l[S]+=m,o[S]+=y,S+=x<<1;M=(v=M)*d-b*w,b=v*w+b*d}x<<=1}S=0;for(var q=a/2;S<q;S++)(i=c*f((e=l[S])*e+(s=o[S])*s))>this.peak&&(this.peakBand=S,this.peak=i),p[S]=i;return p}}class r extends i{static create(t){return new r(t||{})}constructor(t){if(super(t),this.utils={style:(t,e)=>Object.assign(t.style,e)},this.drawSpectrogram=t=>{isNaN(t[0][0])||(t=[t]);const e=this.spectrCc,s=this.height,i=this.width,a=this.buffer.sampleRate/2,r=this.frequencyMin,n=this.frequencyMax;if(e){for(let h=0;h<t.length;h++){const l=this.resample(t[h]),o=new ImageData(i,s);for(let t=0;t<l.length;t++)for(let e=0;e<l[t].length;e++){const a=this.colorMap[l[t][e]],r=4*((s-e)*i+t);o.data[r]=255*a[0],o.data[r+1]=255*a[1],o.data[r+2]=255*a[2],o.data[r+3]=255*a[3]}createImageBitmap(o).then((t=>{e.drawImage(t,0,s*(1-n/a),i,s*(n-r)/a,0,s*h,i,s)}))}this.emit("ready")}},this.frequenciesDataUrl=t.frequenciesDataUrl,this.container="string"==typeof t.container?document.querySelector(t.container):t.container,t.colorMap){if(t.colorMap.length<256)throw new Error("Colormap must contain 256 elements");for(let e=0;e<t.colorMap.length;e++)if(4!==t.colorMap[e].length)throw new Error("ColorMap entries must contain 4 values");this.colorMap=t.colorMap}else{this.colorMap=[];for(let t=0;t<256;t++){const e=(255-t)/256;this.colorMap.push([e,e,e,1])}}this.fftSamples=t.fftSamples||512,this.height=t.height||this.fftSamples/2,this.noverlap=t.noverlap,this.windowFunc=t.windowFunc,this.alpha=t.alpha,this.channels=1,this.frequencyMin=t.frequencyMin||0,this.frequencyMax=t.frequencyMax||0,this.createWrapper(),this.createCanvas()}onInit(){this.container=this.container||this.wavesurfer.getWrapper(),this.container.appendChild(this.wrapper),this.wavesurfer.options.fillParent&&this.utils.style(this.wrapper,{width:"100%",overflowX:"hidden",overflowY:"hidden"}),this.width=this.wavesurfer.getWrapper().offsetWidth,this.subscriptions.push(this.wavesurfer.on("redraw",(()=>this.render())))}destroy(){this.unAll(),this.wavesurfer.un("ready",this._onReady),this.wavesurfer.un("redraw",this._onRender),this.wavesurfer=null,this.util=null,this.options=null,this.wrapper&&(this.wrapper.remove(),this.wrapper=null)}createWrapper(){if(this.wrapper=document.createElement("div"),this.utils.style(this.wrapper,{display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none",height:this.height*this.channels+"px"}),this.options.labels){const t=document.createElement("canvas");t.classList.add("spec-labels"),this.utils.style(t,{position:"absolute",zIndex:9,width:"55px",height:"100%"}),this.wrapper.appendChild(t),this.labelsEl=t}this.wrapper.addEventListener("click",this._onWrapperClick)}_wrapperClickHandler(t){t.preventDefault();const e="offsetX"in t?t.offsetX:t.layerX;this.emit("click",e/this.width||0)}createCanvas(){const t=document.createElement("canvas");this.wrapper.appendChild(t),this.spectrCc=t.getContext("2d"),this.utils.style(t,{position:"absolute",left:0,top:0,width:"100%",height:"100%",zIndex:4}),this.canvas=t}render(){this.canvas.width=this.width,this.canvas.height=this.height,this.frequenciesDataUrl?this.loadFrequenciesData(this.frequenciesDataUrl):this.getFrequencies(this.drawSpectrogram),this.loadLabels("rgba(68,68,68,0.5)","12px","10px","","#fff","#f7f7f7","center","#specLabels")}getFrequencies(t){const e=this.fftSamples,s=this.wavesurfer.getDecodedData(),i=this.channels;if(this.frequencyMax=this.frequencyMax||s.sampleRate/2,!s)return;this.buffer=s;const r=s.sampleRate,n=[];let h=this.noverlap;if(!h){const t=s.length/this.canvas.width;h=Math.max(0,Math.round(e-t))}const l=new a(e,r,this.windowFunc,this.alpha);for(let t=0;t<i;t++){const i=s.getChannelData(t),a=[];let r=0;for(;r+e<i.length;){const t=i.slice(r,r+e),s=l.calculateSpectrum(t),n=new Uint8Array(e/2);let o;for(o=0;o<e/2;o++)n[o]=Math.max(-255,45*Math.log10(s[o]));a.push(n),r+=e-h}n.push(a)}t(n,this)}loadFrequenciesData(t){return fetch(t).then((t=>t.json())).then(this.drawSpectrogram)}freqType(t){return t>=1e3?(t/1e3).toFixed(1):Math.round(t)}unitType(t){return t>=1e3?"KHz":"Hz"}loadLabels(t,e,s,i,a,r,n,h){t=t||"rgba(68,68,68,0)",e=e||"12px",s=s||"10px",i=i||"Helvetica",a=a||"#fff",r=r||"#fff",n=n||"center",h=h||"#specLabels";const l=this.height||512,o=l/256*5,c=this.frequencyMin,f=(this.frequencyMax-c)/o,p=this.labelsEl.getContext("2d"),u=window.devicePixelRatio;if(this.labelsEl.height=this.height*this.channels*u,this.labelsEl.width=55*u,p.scale(u,u),p)for(let h=0;h<this.channels;h++){let u;for(p.fillStyle=t,p.fillRect(0,h*l,55,(1+h)*l),p.fill(),u=0;u<=o;u++){p.textAlign=n,p.textBaseline="middle";const t=c+f*u,o=this.freqType(t),d=this.unitType(t),w=2,M=16;let b;0==u?(b=(1+h)*l+u-10,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,M+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,M,b)):(b=(1+h)*l-50*u+w,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,M+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,M,b))}}}resample(t){const e=this.width,s=[],i=1/t.length,a=1/e;let r;for(r=0;r<e;r++){const e=new Array(t[0].length);let n;for(n=0;n<t.length;n++){const s=n*i,h=s+i,l=r*a,o=l+a,c=h<=l||o<=s?0:Math.min(Math.max(h,l),Math.max(o,s))-Math.max(Math.min(h,l),Math.min(o,s));let f;if(c>0)for(f=0;f<t[0].length;f++)null==e[f]&&(e[f]=0),e[f]+=c/a*t[n][f]}const h=new Uint8Array(t[0].length);let l;for(l=0;l<t[0].length;l++)h[l]=e[l];s.push(h)}return s}}return e.default})()));