wavesurfer.js 7.12.3 → 7.12.5

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,967 +1 @@
1
- /**
2
- * Windowed Spectrogram plugin - Optimized for very long audio files
3
- *
4
- * Only renders frequency data in a sliding window around the current viewport,
5
- * keeping memory usage constant regardless of audio length.
6
- */
7
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
8
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9
- return new (P || (P = Promise))(function (resolve, reject) {
10
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
11
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
12
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13
- step((generator = generator.apply(thisArg, _arguments || [])).next());
14
- });
15
- };
16
- // @ts-nocheck
17
- import BasePlugin from '../base-plugin.js';
18
- import createElement from '../dom.js';
19
- // Import centralized FFT functionality
20
- import FFT, { hzToScale, scaleToHz, createFilterBankForScale, applyFilterBank, setupColorMap, createWrapperClickHandler, } from '../fft.js';
21
- // Import the worker using rollup-plugin-web-worker-loader
22
- import SpectrogramWorker from 'web-worker:./spectrogram-worker.ts';
23
- class WindowedSpectrogramPlugin extends BasePlugin {
24
- static create(options) {
25
- return new WindowedSpectrogramPlugin(options || {});
26
- }
27
- constructor(options) {
28
- var _a, _b;
29
- super(options);
30
- this.segments = new Map();
31
- this.buffer = null;
32
- this.currentPosition = 0; // current playback position in seconds
33
- this.pixelsPerSecond = 0;
34
- this.isRendering = false;
35
- this.renderTimeout = null;
36
- // FFT and processing
37
- this.fft = null;
38
- // Progressive loading
39
- this.progressiveLoadTimeout = null;
40
- this.isProgressiveLoading = false;
41
- this.nextProgressiveSegmentTime = 0; // Track which segment to load next
42
- // Web worker for FFT calculations
43
- this.worker = null;
44
- this.workerPromises = new Map();
45
- this.qualityUpdateTimeout = null;
46
- this._onWrapperClick = (e) => {
47
- const rect = this.wrapper.getBoundingClientRect();
48
- const relativeX = e.clientX - rect.left;
49
- const relativeWidth = rect.width;
50
- const relativePosition = relativeX / relativeWidth;
51
- this.emit('click', relativePosition);
52
- };
53
- this.container =
54
- 'string' == typeof options.container ? document.querySelector(options.container) : options.container;
55
- // Set up color map using shared utility
56
- this.colorMap = setupColorMap(options.colorMap);
57
- // FFT and processing options
58
- this.fftSamples = options.fftSamples || 512;
59
- this.height = options.height || 200;
60
- this.noverlap = options.noverlap || null; // Will be calculated later based on canvas size, like normal plugin
61
- this.windowFunc = options.windowFunc || 'hann';
62
- this.alpha = options.alpha;
63
- this.frequencyMin = options.frequencyMin || 0;
64
- this.frequencyMax = options.frequencyMax || 0;
65
- this.gainDB = (_a = options.gainDB) !== null && _a !== void 0 ? _a : 20;
66
- this.rangeDB = (_b = options.rangeDB) !== null && _b !== void 0 ? _b : 80;
67
- this.scale = options.scale || 'mel';
68
- // Windowing options
69
- this.windowSize = options.windowSize || 30; // 30 seconds window
70
- this.bufferSize = options.bufferSize || 5000; // 5000 pixels buffer
71
- // Progressive loading (disabled by default to avoid system overload)
72
- this.progressiveLoading = options.progressiveLoading === true;
73
- // Web worker (disabled by default in SSR environments like Next.js)
74
- this.useWebWorker = options.useWebWorker === true && typeof window !== 'undefined';
75
- // Filter banks
76
- this.numMelFilters = this.fftSamples / 2;
77
- this.numLogFilters = this.fftSamples / 2;
78
- this.numBarkFilters = this.fftSamples / 2;
79
- this.numErbFilters = this.fftSamples / 2;
80
- this.createWrapper();
81
- this.createCanvas();
82
- // Initialize worker if enabled
83
- if (this.useWebWorker) {
84
- this.initializeWorker();
85
- }
86
- }
87
- initializeWorker() {
88
- // Skip worker initialization in SSR environments (Next.js server-side)
89
- if (typeof window === 'undefined' || typeof Worker === 'undefined') {
90
- console.warn('Worker not available in this environment, using main thread calculation');
91
- return;
92
- }
93
- try {
94
- // Create worker using imported worker constructor
95
- this.worker = new SpectrogramWorker();
96
- this.worker.onmessage = (e) => {
97
- const { type, id, result, error } = e.data;
98
- if (type === 'frequenciesResult') {
99
- const promise = this.workerPromises.get(id);
100
- if (promise) {
101
- this.workerPromises.delete(id);
102
- if (error) {
103
- promise.reject(new Error(error));
104
- }
105
- else {
106
- promise.resolve(result);
107
- }
108
- }
109
- }
110
- };
111
- this.worker.onerror = (error) => {
112
- console.warn('Spectrogram worker error, falling back to main thread:', error);
113
- // Fallback to main thread calculation
114
- this.worker = null;
115
- };
116
- }
117
- catch (error) {
118
- console.warn('Failed to initialize worker, falling back to main thread:', error);
119
- this.worker = null;
120
- }
121
- }
122
- onInit() {
123
- // Recreate DOM elements if they were destroyed
124
- if (!this.wrapper) {
125
- this.createWrapper();
126
- }
127
- if (!this.canvasContainer) {
128
- this.createCanvas();
129
- }
130
- // Always get fresh container reference to avoid stale references
131
- this.container = this.wavesurfer.getWrapper();
132
- this.container.appendChild(this.wrapper);
133
- // Set up styling
134
- if (this.wavesurfer.options.fillParent) {
135
- Object.assign(this.wrapper.style, {
136
- width: '100%',
137
- overflowX: 'hidden',
138
- overflowY: 'hidden',
139
- });
140
- }
141
- // Listen for playback position changes
142
- this.subscriptions.push(this.wavesurfer.on('timeupdate', (currentTime) => {
143
- this.updatePosition(currentTime);
144
- }));
145
- // Listen for scroll events
146
- this.subscriptions.push(this.wavesurfer.on('scroll', () => {
147
- this.handleScroll();
148
- }));
149
- // Listen for zoom changes
150
- this.subscriptions.push(this.wavesurfer.on('redraw', () => this.handleRedraw()));
151
- // Listen for audio data ready
152
- this.subscriptions.push(this.wavesurfer.on('ready', () => {
153
- const decodedData = this.wavesurfer.getDecodedData();
154
- if (decodedData) {
155
- this.render(decodedData);
156
- }
157
- }));
158
- // Trigger initial render after re-initialization
159
- // This ensures the spectrogram appears even if no redraw event is fired
160
- if (this.wavesurfer.getDecodedData()) {
161
- // Use setTimeout to ensure DOM is fully ready
162
- setTimeout(() => {
163
- this.render(this.wavesurfer.getDecodedData());
164
- }, 0);
165
- }
166
- }
167
- createWrapper() {
168
- this.wrapper = createElement('div', {
169
- style: {
170
- display: 'block',
171
- position: 'relative',
172
- userSelect: 'none',
173
- },
174
- });
175
- // Labels canvas
176
- if (this.options.labels) {
177
- this.labelsEl = createElement('canvas', {
178
- part: 'spec-labels',
179
- style: {
180
- position: 'absolute',
181
- zIndex: 9,
182
- width: '55px',
183
- height: '100%',
184
- },
185
- }, this.wrapper);
186
- }
187
- // Create wrapper click handler using shared utility
188
- this._onWrapperClick = createWrapperClickHandler(this.wrapper, this.emit.bind(this));
189
- this.wrapper.addEventListener('click', this._onWrapperClick);
190
- }
191
- createCanvas() {
192
- this.canvasContainer = createElement('div', {
193
- style: {
194
- position: 'absolute',
195
- left: 0,
196
- top: 0,
197
- width: '100%',
198
- height: '100%',
199
- zIndex: 4,
200
- },
201
- }, this.wrapper);
202
- }
203
- handleRedraw() {
204
- const oldPixelsPerSecond = this.pixelsPerSecond;
205
- this.pixelsPerSecond = this.getPixelsPerSecond();
206
- // Only update canvas positions if zoom changed, keep frequency data!
207
- if (oldPixelsPerSecond !== this.pixelsPerSecond && this.segments.size > 0) {
208
- this.updateSegmentPositions(oldPixelsPerSecond, this.pixelsPerSecond);
209
- }
210
- this.scheduleRender();
211
- }
212
- updateSegmentPositions(oldPxPerSec, newPxPerSec) {
213
- for (const segment of this.segments.values()) {
214
- // Update pixel positions based on new zoom level
215
- segment.startPixel = segment.startTime * newPxPerSec;
216
- segment.endPixel = segment.endTime * newPxPerSec;
217
- // Update canvas positioning and size WITHOUT recalculating frequencies
218
- if (segment.canvas) {
219
- const segmentWidth = segment.endPixel - segment.startPixel;
220
- segment.canvas.style.left = `${segment.startPixel}px`;
221
- segment.canvas.style.width = `${segmentWidth}px`;
222
- }
223
- }
224
- // Schedule a gentle re-render of visible segments only if zoom changed significantly
225
- const zoomRatio = newPxPerSec / oldPxPerSec;
226
- if (zoomRatio < 0.5 || zoomRatio > 2.0) {
227
- this.scheduleSegmentQualityUpdate();
228
- }
229
- }
230
- scheduleSegmentQualityUpdate() {
231
- // Debounce quality updates to avoid rapid re-renders during zoom
232
- if (this.qualityUpdateTimeout) {
233
- clearTimeout(this.qualityUpdateTimeout);
234
- }
235
- this.qualityUpdateTimeout = window.setTimeout(() => {
236
- this.updateVisibleSegmentQuality();
237
- }, 500); // Wait 500ms after zoom stops
238
- }
239
- updateVisibleSegmentQuality() {
240
- return __awaiter(this, void 0, void 0, function* () {
241
- var _a;
242
- if (!this.buffer)
243
- return;
244
- const wrapper = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper();
245
- if (!wrapper)
246
- return;
247
- // Get current viewport
248
- const scrollLeft = this.getScrollLeft(wrapper);
249
- const viewportWidth = this.getViewportWidth(wrapper);
250
- const pixelsPerSec = this.getPixelsPerSecond();
251
- const visibleStartTime = scrollLeft / pixelsPerSec;
252
- const visibleEndTime = (scrollLeft + viewportWidth) / pixelsPerSec;
253
- // Find segments that overlap with visible area
254
- const visibleSegments = Array.from(this.segments.values()).filter((segment) => segment.startTime < visibleEndTime && segment.endTime > visibleStartTime);
255
- if (visibleSegments.length === 0) {
256
- return;
257
- }
258
- // Re-render only the visible segments with current zoom level
259
- for (const segment of visibleSegments) {
260
- if (segment.canvas) {
261
- yield this.renderSegment(segment);
262
- }
263
- }
264
- });
265
- }
266
- getScrollLeft(wrapper) {
267
- var _a;
268
- // Try multiple sources for scroll position
269
- if (wrapper.scrollLeft)
270
- return wrapper.scrollLeft;
271
- if ((_a = wrapper.parentElement) === null || _a === void 0 ? void 0 : _a.scrollLeft)
272
- return wrapper.parentElement.scrollLeft;
273
- if (document.documentElement.scrollLeft)
274
- return document.documentElement.scrollLeft;
275
- if (document.body.scrollLeft)
276
- return document.body.scrollLeft;
277
- if (window.scrollX)
278
- return window.scrollX;
279
- if (window.pageXOffset)
280
- return window.pageXOffset;
281
- // Look for scrollable ancestors
282
- let element = wrapper.parentElement;
283
- while (element) {
284
- const computedStyle = window.getComputedStyle(element);
285
- if (computedStyle.overflowX === 'scroll' || computedStyle.overflowX === 'auto') {
286
- if (element.scrollLeft > 0)
287
- return element.scrollLeft;
288
- }
289
- element = element.parentElement;
290
- }
291
- return 0;
292
- }
293
- getViewportWidth(wrapper) {
294
- var _a, _b;
295
- const wrapperWidth = wrapper.offsetWidth || wrapper.clientWidth;
296
- const parentWidth = ((_a = wrapper.parentElement) === null || _a === void 0 ? void 0 : _a.offsetWidth) || ((_b = wrapper.parentElement) === null || _b === void 0 ? void 0 : _b.clientWidth);
297
- const windowWidth = window.innerWidth;
298
- // Use the smallest reasonable width
299
- if (parentWidth && parentWidth < wrapperWidth)
300
- return parentWidth;
301
- return Math.min(wrapperWidth || 800, windowWidth * 0.8);
302
- }
303
- handleScroll() {
304
- var _a, _b;
305
- const wrapper = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper();
306
- // Use the same scroll detection logic as renderVisibleWindow
307
- let scrollLeft = 0;
308
- if (wrapper === null || wrapper === void 0 ? void 0 : wrapper.scrollLeft) {
309
- scrollLeft = wrapper.scrollLeft;
310
- }
311
- else if ((_b = wrapper === null || wrapper === void 0 ? void 0 : wrapper.parentElement) === null || _b === void 0 ? void 0 : _b.scrollLeft) {
312
- scrollLeft = wrapper.parentElement.scrollLeft;
313
- }
314
- else if (document.documentElement.scrollLeft || document.body.scrollLeft) {
315
- scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
316
- }
317
- else if (window.scrollX || window.pageXOffset) {
318
- scrollLeft = window.scrollX || window.pageXOffset;
319
- }
320
- else if (wrapper) {
321
- // Look for scrollable ancestors
322
- let element = wrapper.parentElement;
323
- while (element && scrollLeft === 0) {
324
- const computedStyle = window.getComputedStyle(element);
325
- if (computedStyle.overflowX === 'scroll' || computedStyle.overflowX === 'auto') {
326
- if (element.scrollLeft > 0) {
327
- scrollLeft = element.scrollLeft;
328
- break;
329
- }
330
- }
331
- element = element.parentElement;
332
- }
333
- }
334
- const pixelsPerSec = this.getPixelsPerSecond();
335
- const currentViewTime = scrollLeft / pixelsPerSec;
336
- this.scheduleRender();
337
- }
338
- updatePosition(currentTime) {
339
- this.currentPosition = currentTime;
340
- this.scheduleRender();
341
- }
342
- scheduleRender() {
343
- if (this.renderTimeout) {
344
- clearTimeout(this.renderTimeout);
345
- }
346
- this.renderTimeout = window.setTimeout(() => {
347
- this.renderVisibleWindow();
348
- }, 16); // 60fps
349
- }
350
- renderVisibleWindow() {
351
- return __awaiter(this, void 0, void 0, function* () {
352
- var _a;
353
- if (this.isRendering || !this.buffer)
354
- return;
355
- this.isRendering = true;
356
- try {
357
- const wrapper = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper();
358
- if (!wrapper)
359
- return;
360
- // Use helper functions for consistency
361
- const scrollLeft = this.getScrollLeft(wrapper);
362
- const actualViewportWidth = this.getViewportWidth(wrapper);
363
- const pixelsPerSec = this.getPixelsPerSecond();
364
- const totalAudioDuration = this.buffer.duration;
365
- const visibleStartTime = scrollLeft / pixelsPerSec;
366
- const visibleEndTime = (scrollLeft + actualViewportWidth) / pixelsPerSec;
367
- // Reasonable buffer time based on visible duration
368
- const visibleDuration = visibleEndTime - visibleStartTime;
369
- let bufferTimeSeconds = Math.min(2, visibleDuration * 0.5); // Buffer is at most half the visible duration or 2s
370
- if (visibleDuration > 30) {
371
- console.warn(`⚠️ Large visible duration: ${visibleDuration.toFixed(1)}s - limiting buffer`);
372
- bufferTimeSeconds = 1; // Smaller buffer for very zoomed out views
373
- }
374
- const windowStartTime = Math.max(0, visibleStartTime - bufferTimeSeconds);
375
- const windowEndTime = Math.min(this.buffer.duration, visibleEndTime + bufferTimeSeconds);
376
- // Generate segments for this window
377
- yield this.generateSegments(windowStartTime, windowEndTime);
378
- // Don't clean up old segments - keep them all in memory for performance
379
- }
380
- finally {
381
- this.isRendering = false;
382
- }
383
- });
384
- }
385
- generateSegments(startTime, endTime) {
386
- return __awaiter(this, void 0, void 0, function* () {
387
- if (!this.buffer)
388
- return;
389
- const pixelsPerSec = this.getPixelsPerSecond();
390
- const containerWidth = this.getWidth(); // Get full container width
391
- const totalAudioDuration = this.buffer.duration;
392
- // Progressive loading always uses fixed segment sizes, never fill container mode
393
- const isProgressiveLoadCall = this.isProgressiveLoading && endTime - startTime <= 35; // Progressive loading uses ~30s segments
394
- // Calculate if this is a short audio that should fill the container
395
- const totalAudioPixelWidth = totalAudioDuration * pixelsPerSec;
396
- const shouldFillContainer = !isProgressiveLoadCall && totalAudioPixelWidth <= containerWidth && totalAudioDuration <= 60; // 60s max for fill mode
397
- let segmentPixelWidth;
398
- let segmentDuration;
399
- if (isProgressiveLoadCall) {
400
- // For progressive loading, respect the requested time range exactly
401
- segmentDuration = endTime - startTime;
402
- segmentPixelWidth = segmentDuration * pixelsPerSec;
403
- }
404
- else if (shouldFillContainer) {
405
- // For short audio, create one segment that fills the entire container width
406
- segmentPixelWidth = containerWidth;
407
- segmentDuration = totalAudioDuration; // Use full audio duration
408
- }
409
- else {
410
- // For long audio viewport rendering, use windowing approach
411
- segmentPixelWidth = 15000; // 15000 pixels per segment
412
- segmentDuration = segmentPixelWidth / pixelsPerSec; // Calculate duration based on pixel width
413
- }
414
- // Show existing segments
415
- if (this.segments.size > 0) {
416
- for (const [key, segment] of this.segments) {
417
- }
418
- }
419
- // Check coverage first to avoid duplicate work
420
- const uncoveredRanges = this.findUncoveredTimeRanges(startTime, endTime, segmentDuration);
421
- if (uncoveredRanges.length === 0) {
422
- return;
423
- }
424
- let newSegmentsCreated = 0;
425
- // Only generate segments for uncovered ranges
426
- for (const range of uncoveredRanges) {
427
- // Create segments covering this uncovered range
428
- for (let time = range.start; time < range.end; time += segmentDuration) {
429
- const segmentStart = time;
430
- const segmentEnd = Math.min(time + segmentDuration, range.end, this.buffer.duration);
431
- const segmentKey = `${Math.floor(segmentStart * 10)}_${Math.floor(segmentEnd * 10)}`;
432
- // Double-check if segment already exists (shouldn't happen but be safe)
433
- if (this.segments.has(segmentKey)) {
434
- continue;
435
- }
436
- newSegmentsCreated++;
437
- // Calculate frequency data for this segment
438
- const freqStartTime = performance.now();
439
- const frequencies = yield this.calculateFrequencies(segmentStart, segmentEnd);
440
- const freqEndTime = performance.now();
441
- if (frequencies && frequencies.length > 0) {
442
- const segment = {
443
- startTime: segmentStart,
444
- endTime: segmentEnd,
445
- startPixel: shouldFillContainer ? 0 : segmentStart * pixelsPerSec, // Start at 0 for fill mode
446
- endPixel: shouldFillContainer ? containerWidth : segmentEnd * pixelsPerSec, // End at container width for fill mode
447
- frequencies: frequencies,
448
- };
449
- this.segments.set(segmentKey, segment);
450
- // Render this segment
451
- const renderStartTime = performance.now();
452
- yield this.renderSegment(segment);
453
- const renderEndTime = performance.now();
454
- // Emit progress update
455
- this.emitProgress();
456
- }
457
- else {
458
- }
459
- }
460
- }
461
- // Start progressive loading if not already running
462
- if (!this.isProgressiveLoading) {
463
- this.startProgressiveLoading();
464
- }
465
- });
466
- }
467
- findUncoveredTimeRanges(startTime, endTime, segmentDuration) {
468
- // Get all existing segments sorted by start time
469
- const existingSegments = Array.from(this.segments.values()).sort((a, b) => a.startTime - b.startTime);
470
- const uncoveredRanges = [];
471
- let currentTime = startTime;
472
- for (const segment of existingSegments) {
473
- // If there's a gap before this segment
474
- if (currentTime < segment.startTime && currentTime < endTime) {
475
- const gapEnd = Math.min(segment.startTime, endTime);
476
- uncoveredRanges.push({
477
- start: currentTime,
478
- end: gapEnd,
479
- });
480
- }
481
- // Move past this segment
482
- currentTime = Math.max(currentTime, segment.endTime);
483
- // If we've covered the requested range, stop
484
- if (currentTime >= endTime) {
485
- break;
486
- }
487
- }
488
- // If there's still uncovered time at the end
489
- if (currentTime < endTime) {
490
- uncoveredRanges.push({
491
- start: currentTime,
492
- end: endTime,
493
- });
494
- }
495
- return uncoveredRanges;
496
- }
497
- startProgressiveLoading() {
498
- if (this.isProgressiveLoading || !this.buffer || !this.progressiveLoading)
499
- return;
500
- this.isProgressiveLoading = true;
501
- this.nextProgressiveSegmentTime = 0; // Start from the beginning
502
- // Start loading after a short delay to not interfere with user interactions
503
- this.progressiveLoadTimeout = window.setTimeout(() => {
504
- this.progressiveLoadNextSegment();
505
- }, 1000); // Wait 1 second before starting
506
- }
507
- progressiveLoadNextSegment() {
508
- return __awaiter(this, void 0, void 0, function* () {
509
- if (!this.buffer || !this.isProgressiveLoading)
510
- return;
511
- // For progressive loading, use fixed time-based segments (not pixel-based)
512
- const segmentDuration = 30; // 30 seconds per segment for progressive loading
513
- const totalDuration = this.buffer.duration;
514
- // Check if we've reached the end
515
- if (this.nextProgressiveSegmentTime >= totalDuration) {
516
- this._stopProgressiveLoading();
517
- return;
518
- }
519
- const segmentStart = this.nextProgressiveSegmentTime;
520
- const segmentEnd = Math.min(segmentStart + segmentDuration, totalDuration);
521
- // Check if this segment is already loaded
522
- const segmentKey = `${Math.floor(segmentStart * 10)}_${Math.floor(segmentEnd * 10)}`;
523
- const isAlreadyLoaded = this.segments.has(segmentKey);
524
- if (!isAlreadyLoaded) {
525
- try {
526
- yield this.generateSegments(segmentStart, segmentEnd);
527
- }
528
- catch (error) {
529
- console.warn('Progressive loading failed:', error);
530
- this._stopProgressiveLoading();
531
- return;
532
- }
533
- }
534
- // Move to next segment
535
- this.nextProgressiveSegmentTime = segmentEnd;
536
- // Schedule next progressive load
537
- this.progressiveLoadTimeout = window.setTimeout(() => {
538
- this.progressiveLoadNextSegment();
539
- }, 2000); // Wait 2 seconds between segments
540
- });
541
- }
542
- _stopProgressiveLoading() {
543
- this.isProgressiveLoading = false;
544
- if (this.progressiveLoadTimeout) {
545
- clearTimeout(this.progressiveLoadTimeout);
546
- this.progressiveLoadTimeout = null;
547
- }
548
- }
549
- /** Get the current loading progress as a percentage (0-100) */
550
- getLoadingProgress() {
551
- if (!this.buffer)
552
- return 0;
553
- const totalDuration = this.buffer.duration;
554
- if (totalDuration === 0)
555
- return 100;
556
- if (!this.isProgressiveLoading && this.segments.size === 0)
557
- return 0;
558
- // Calculate progress based on how far we've progressed through the audio
559
- const progress = Math.min(100, (this.nextProgressiveSegmentTime / totalDuration) * 100);
560
- // If progressive loading is complete, return 100%
561
- if (!this.isProgressiveLoading && this.nextProgressiveSegmentTime >= totalDuration) {
562
- return 100;
563
- }
564
- return progress;
565
- }
566
- emitProgress() {
567
- const progress = this.getLoadingProgress() / 100; // Convert to 0-1 range
568
- this.emit('progress', progress);
569
- }
570
- calculateFrequencies(startTime, endTime) {
571
- return __awaiter(this, void 0, void 0, function* () {
572
- if (!this.buffer)
573
- return [];
574
- const calcStartTime = performance.now();
575
- const sampleRate = this.buffer.sampleRate;
576
- const channels = this.options.splitChannels ? this.buffer.numberOfChannels : 1;
577
- // Try to use web worker first
578
- if (this.worker) {
579
- try {
580
- const result = yield this.calculateFrequenciesWithWorker(startTime, endTime);
581
- const totalTime = performance.now() - calcStartTime;
582
- return result;
583
- }
584
- catch (error) {
585
- console.warn('Worker calculation failed, falling back to main thread:', error);
586
- // Fall through to main thread calculation
587
- }
588
- }
589
- // Fallback to main thread calculation
590
- return this.calculateFrequenciesMainThread(startTime, endTime);
591
- });
592
- }
593
- calculateFrequenciesWithWorker(startTime, endTime) {
594
- return __awaiter(this, void 0, void 0, function* () {
595
- if (!this.buffer || !this.worker) {
596
- throw new Error('Worker not available');
597
- }
598
- const sampleRate = this.buffer.sampleRate;
599
- const channels = this.options.splitChannels ? this.buffer.numberOfChannels : 1;
600
- // Calculate noverlap
601
- let noverlap = this.noverlap;
602
- if (!noverlap) {
603
- const segmentDuration = endTime - startTime;
604
- const pixelsPerSec = this.getPixelsPerSecond();
605
- const segmentWidth = segmentDuration * pixelsPerSec;
606
- const startSample = Math.floor(startTime * sampleRate);
607
- const endSample = Math.floor(endTime * sampleRate);
608
- const uniqueSamplesPerPx = (endSample - startSample) / segmentWidth;
609
- noverlap = Math.max(0, Math.round(this.fftSamples - uniqueSamplesPerPx));
610
- }
611
- // Prepare audio data for worker
612
- const audioData = [];
613
- for (let c = 0; c < channels; c++) {
614
- audioData.push(this.buffer.getChannelData(c));
615
- }
616
- // Generate unique ID for this request
617
- const id = `${Date.now()}_${Math.random()}`;
618
- // Create promise for worker response
619
- const promise = new Promise((resolve, reject) => {
620
- this.workerPromises.set(id, { resolve, reject });
621
- // Set timeout to avoid hanging
622
- setTimeout(() => {
623
- if (this.workerPromises.has(id)) {
624
- this.workerPromises.delete(id);
625
- reject(new Error('Worker timeout'));
626
- }
627
- }, 30000); // 30 second timeout
628
- });
629
- // Send message to worker
630
- this.worker.postMessage({
631
- type: 'calculateFrequencies',
632
- id,
633
- audioData,
634
- options: {
635
- startTime,
636
- endTime,
637
- sampleRate,
638
- fftSamples: this.fftSamples,
639
- windowFunc: this.windowFunc,
640
- alpha: this.alpha,
641
- noverlap,
642
- scale: this.scale,
643
- gainDB: this.gainDB,
644
- rangeDB: this.rangeDB,
645
- splitChannels: this.options.splitChannels || false,
646
- },
647
- });
648
- return promise;
649
- });
650
- }
651
- calculateFrequenciesMainThread(startTime, endTime) {
652
- return __awaiter(this, void 0, void 0, function* () {
653
- if (!this.buffer)
654
- return [];
655
- const sampleRate = this.buffer.sampleRate;
656
- const startSample = Math.floor(startTime * sampleRate);
657
- const endSample = Math.floor(endTime * sampleRate);
658
- const channels = this.options.splitChannels ? this.buffer.numberOfChannels : 1;
659
- // Initialize FFT if needed
660
- if (!this.fft) {
661
- this.fft = new FFT(this.fftSamples, sampleRate, this.windowFunc, this.alpha);
662
- }
663
- // Calculate noverlap like the normal plugin
664
- let noverlap = this.noverlap;
665
- if (!noverlap) {
666
- const segmentDuration = endTime - startTime;
667
- const pixelsPerSec = this.getPixelsPerSecond();
668
- const segmentWidth = segmentDuration * pixelsPerSec;
669
- const uniqueSamplesPerPx = (endSample - startSample) / segmentWidth;
670
- noverlap = Math.max(0, Math.round(this.fftSamples - uniqueSamplesPerPx));
671
- }
672
- // OPTIMIZATION: For windowed mode, reduce overlap to speed up processing
673
- const maxOverlap = this.fftSamples * 0.5;
674
- noverlap = Math.min(noverlap, maxOverlap);
675
- const minHopSize = Math.max(64, this.fftSamples * 0.25);
676
- const hopSize = Math.max(minHopSize, this.fftSamples - noverlap);
677
- const frequencies = [];
678
- const fftStartTime = performance.now();
679
- let totalFFTs = 0;
680
- for (let c = 0; c < channels; c++) {
681
- const channelData = this.buffer.getChannelData(c);
682
- const channelFreq = [];
683
- for (let sample = startSample; sample + this.fftSamples < endSample; sample += hopSize) {
684
- const segment = channelData.slice(sample, sample + this.fftSamples);
685
- let spectrum = this.fft.calculateSpectrum(segment);
686
- totalFFTs++;
687
- // Apply filter bank if needed
688
- const filterBank = this.getFilterBank(sampleRate);
689
- if (filterBank) {
690
- spectrum = applyFilterBank(spectrum, filterBank);
691
- }
692
- // Convert to uint8 color indices
693
- const freqBins = new Uint8Array(spectrum.length);
694
- const gainPlusRange = this.gainDB + this.rangeDB;
695
- for (let j = 0; j < spectrum.length; j++) {
696
- const magnitude = spectrum[j] > 1e-12 ? spectrum[j] : 1e-12;
697
- const valueDB = 20 * Math.log10(magnitude);
698
- if (valueDB < -gainPlusRange) {
699
- freqBins[j] = 0;
700
- }
701
- else if (valueDB > -this.gainDB) {
702
- freqBins[j] = 255;
703
- }
704
- else {
705
- freqBins[j] = Math.round(((valueDB + this.gainDB) / this.rangeDB) * 255);
706
- }
707
- }
708
- channelFreq.push(freqBins);
709
- }
710
- frequencies.push(channelFreq);
711
- }
712
- const fftEndTime = performance.now();
713
- return frequencies;
714
- });
715
- }
716
- renderSegment(segment) {
717
- return __awaiter(this, void 0, void 0, function* () {
718
- var _a;
719
- const segmentWidth = segment.endPixel - segment.startPixel;
720
- const totalHeight = this.height * segment.frequencies.length;
721
- // Create canvas for this segment
722
- const canvas = document.createElement('canvas');
723
- canvas.width = Math.round(segmentWidth);
724
- canvas.height = Math.round(totalHeight);
725
- canvas.style.position = 'absolute';
726
- canvas.style.left = `${segment.startPixel}px`;
727
- canvas.style.top = '0';
728
- canvas.style.width = `${segmentWidth}px`;
729
- canvas.style.height = `${totalHeight}px`;
730
- const ctx = canvas.getContext('2d');
731
- if (!ctx)
732
- return;
733
- // Get frequency scaling parameters like the normal plugin
734
- const freqFrom = ((_a = this.buffer) === null || _a === void 0 ? void 0 : _a.sampleRate) ? this.buffer.sampleRate / 2 : 0;
735
- const freqMin = this.frequencyMin;
736
- const freqMax = this.frequencyMax || freqFrom;
737
- // Render frequency data to canvas with proper scaling
738
- for (let c = 0; c < segment.frequencies.length; c++) {
739
- yield this.renderChannelToCanvas(segment.frequencies[c], ctx, segmentWidth, this.height, c * this.height, freqFrom, freqMin, freqMax);
740
- }
741
- // Add canvas to container
742
- segment.canvas = canvas;
743
- this.canvasContainer.appendChild(canvas);
744
- });
745
- }
746
- renderChannelToCanvas(channelFreq, ctx, width, height, yOffset, freqFrom, freqMin, freqMax) {
747
- return __awaiter(this, void 0, void 0, function* () {
748
- if (channelFreq.length === 0)
749
- return;
750
- const freqBins = channelFreq[0].length;
751
- const imageData = new ImageData(channelFreq.length, freqBins);
752
- const data = imageData.data;
753
- // Fill image data
754
- for (let i = 0; i < channelFreq.length; i++) {
755
- const column = channelFreq[i];
756
- for (let j = 0; j < freqBins; j++) {
757
- const colorIndex = Math.min(255, Math.max(0, column[j]));
758
- const color = this.colorMap[colorIndex];
759
- const pixelIndex = ((freqBins - j - 1) * channelFreq.length + i) * 4;
760
- data[pixelIndex] = color[0] * 255;
761
- data[pixelIndex + 1] = color[1] * 255;
762
- data[pixelIndex + 2] = color[2] * 255;
763
- data[pixelIndex + 3] = color[3] * 255;
764
- }
765
- }
766
- // Calculate frequency scaling like the normal plugin
767
- const rMin = hzToScale(freqMin, this.scale) / hzToScale(freqFrom, this.scale);
768
- const rMax = hzToScale(freqMax, this.scale) / hzToScale(freqFrom, this.scale);
769
- const rMax1 = Math.min(1, rMax);
770
- // Use the same frequency scaling approach as the regular spectrogram plugin
771
- const drawHeight = (height * rMax1) / rMax;
772
- const drawY = yOffset + height * (1 - rMax1 / rMax);
773
- const bitmapSourceY = Math.round(freqBins * (1 - rMax1));
774
- const bitmapSourceHeight = Math.round(freqBins * (rMax1 - rMin));
775
- // Create and draw bitmap with proper frequency scaling
776
- const bitmap = yield createImageBitmap(imageData, 0, bitmapSourceY, channelFreq.length, bitmapSourceHeight);
777
- ctx.drawImage(bitmap, 0, drawY, width, drawHeight);
778
- // Clean up
779
- if ('close' in bitmap) {
780
- bitmap.close();
781
- }
782
- });
783
- }
784
- clearAllSegments() {
785
- for (const segment of this.segments.values()) {
786
- if (segment.canvas) {
787
- segment.canvas.remove();
788
- }
789
- }
790
- this.segments.clear();
791
- }
792
- getFilterBank(sampleRate) {
793
- const numFilters = this.fftSamples / 2;
794
- return createFilterBankForScale(this.scale, numFilters, this.fftSamples, sampleRate);
795
- }
796
- freqType(freq) {
797
- return freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq);
798
- }
799
- unitType(freq) {
800
- return freq >= 1000 ? 'kHz' : 'Hz';
801
- }
802
- getLabelFrequency(index, labelIndex) {
803
- const scaleMin = hzToScale(this.frequencyMin, this.scale);
804
- const scaleMax = hzToScale(this.frequencyMax, this.scale);
805
- return scaleToHz(scaleMin + (index / labelIndex) * (scaleMax - scaleMin), this.scale);
806
- }
807
- loadLabels(bgFill, fontSizeFreq, fontSizeUnit, fontType, textColorFreq, textColorUnit, textAlign, container, channels) {
808
- const frequenciesHeight = this.height;
809
- bgFill = bgFill || 'rgba(68,68,68,0)';
810
- fontSizeFreq = fontSizeFreq || '12px';
811
- fontSizeUnit = fontSizeUnit || '12px';
812
- fontType = fontType || 'Helvetica';
813
- textColorFreq = textColorFreq || '#fff';
814
- textColorUnit = textColorUnit || '#fff';
815
- textAlign = textAlign || 'center';
816
- container = container || '#specLabels';
817
- const bgWidth = 55;
818
- const getMaxY = frequenciesHeight || 512;
819
- const labelIndex = 5 * (getMaxY / 256);
820
- const freqStart = this.frequencyMin;
821
- // prepare canvas element for labels
822
- const ctx = this.labelsEl.getContext('2d');
823
- const dispScale = window.devicePixelRatio;
824
- this.labelsEl.height = this.height * channels * dispScale;
825
- this.labelsEl.width = bgWidth * dispScale;
826
- ctx.scale(dispScale, dispScale);
827
- if (!ctx) {
828
- return;
829
- }
830
- for (let c = 0; c < channels; c++) {
831
- // for each channel
832
- // fill background
833
- ctx.fillStyle = bgFill;
834
- ctx.fillRect(0, c * getMaxY, bgWidth, (1 + c) * getMaxY);
835
- ctx.fill();
836
- let i;
837
- // render labels
838
- for (i = 0; i <= labelIndex; i++) {
839
- ctx.textAlign = textAlign;
840
- ctx.textBaseline = 'middle';
841
- const freq = this.getLabelFrequency(i, labelIndex);
842
- const label = this.freqType(freq);
843
- const units = this.unitType(freq);
844
- const x = 16;
845
- let y = (1 + c) * getMaxY - (i / labelIndex) * getMaxY;
846
- // Make sure label remains in view
847
- y = Math.min(Math.max(y, c * getMaxY + 10), (1 + c) * getMaxY - 10);
848
- // unit label
849
- ctx.fillStyle = textColorUnit;
850
- ctx.font = fontSizeUnit + ' ' + fontType;
851
- ctx.fillText(units, x + 24, y);
852
- // freq label
853
- ctx.fillStyle = textColorFreq;
854
- ctx.font = fontSizeFreq + ' ' + fontType;
855
- ctx.fillText(label.toString(), x, y);
856
- }
857
- }
858
- }
859
- render(audioData) {
860
- return __awaiter(this, void 0, void 0, function* () {
861
- this.buffer = audioData;
862
- this.pixelsPerSecond = this.getPixelsPerSecond();
863
- this.frequencyMax = this.frequencyMax || audioData.sampleRate / 2;
864
- // Set wrapper height
865
- const channels = this.options.splitChannels ? audioData.numberOfChannels : 1;
866
- this.wrapper.style.height = this.height * channels + 'px';
867
- // Clear existing data and reset progressive loading
868
- this.clearAllSegments();
869
- this.nextProgressiveSegmentTime = 0;
870
- // Render frequency labels if enabled
871
- if (this.options.labels) {
872
- this.loadLabels(this.options.labelsBackground, '12px', '12px', '', this.options.labelsColor, this.options.labelsHzColor || this.options.labelsColor, 'center', '#specLabels', channels);
873
- }
874
- // Start initial render
875
- this.scheduleRender();
876
- this.emit('ready');
877
- });
878
- }
879
- destroy() {
880
- this.unAll();
881
- if (this.renderTimeout) {
882
- clearTimeout(this.renderTimeout);
883
- this.renderTimeout = null;
884
- }
885
- if (this.qualityUpdateTimeout) {
886
- clearTimeout(this.qualityUpdateTimeout);
887
- this.qualityUpdateTimeout = null;
888
- }
889
- // Stop progressive loading
890
- this.stopProgressiveLoading();
891
- this.nextProgressiveSegmentTime = 0;
892
- // Clean up worker
893
- if (this.worker) {
894
- this.worker.terminate();
895
- this.worker = null;
896
- }
897
- // Clear any pending worker promises
898
- for (const [id, promise] of this.workerPromises) {
899
- promise.reject(new Error('Plugin destroyed'));
900
- }
901
- this.workerPromises.clear();
902
- this.clearAllSegments();
903
- // Clean up DOM elements properly
904
- if (this.canvasContainer) {
905
- this.canvasContainer.remove();
906
- this.canvasContainer = null;
907
- }
908
- if (this.wrapper) {
909
- this.wrapper.remove();
910
- this.wrapper = null;
911
- }
912
- if (this.labelsEl) {
913
- this.labelsEl.remove();
914
- this.labelsEl = null;
915
- }
916
- // Reset state for potential re-initialization
917
- this.container = null;
918
- this.buffer = null;
919
- this.fft = null;
920
- this.isRendering = false;
921
- this.currentPosition = 0;
922
- this.pixelsPerSecond = 0;
923
- super.destroy();
924
- }
925
- // Add width calculation methods like the normal plugin
926
- getWidth() {
927
- var _a, _b;
928
- return ((_b = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper()) === null || _b === void 0 ? void 0 : _b.offsetWidth) || 0;
929
- }
930
- getPixelsPerSecond() {
931
- var _a;
932
- // Handle default case when no zoom is specified
933
- const minPxPerSec = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.options.minPxPerSec;
934
- if (minPxPerSec && minPxPerSec > 0) {
935
- return minPxPerSec;
936
- }
937
- // For windowed mode, enforce a minimum zoom level so we never try to fit entire audio on screen
938
- const WINDOWED_MIN_PX_PER_SEC = 50; // At least 50 pixels per second for windowed mode
939
- // Fallback: calculate based on wrapper width and audio duration
940
- if (this.buffer) {
941
- const wrapperWidth = this.getWidth();
942
- const calculatedPxPerSec = wrapperWidth > 0 ? wrapperWidth / this.buffer.duration : 100;
943
- // For windowed mode, we want to show only a small portion of audio at a time
944
- // Use the maximum of calculated value and our minimum to ensure reasonable zoom
945
- const finalPxPerSec = Math.max(calculatedPxPerSec, WINDOWED_MIN_PX_PER_SEC);
946
- return finalPxPerSec;
947
- }
948
- return WINDOWED_MIN_PX_PER_SEC;
949
- }
950
- /** Stop progressive loading if it's currently running */
951
- stopProgressiveLoading() {
952
- this.isProgressiveLoading = false;
953
- if (this.progressiveLoadTimeout) {
954
- clearTimeout(this.progressiveLoadTimeout);
955
- this.progressiveLoadTimeout = null;
956
- }
957
- }
958
- /** Restart progressive loading from the beginning */
959
- restartProgressiveLoading() {
960
- this.stopProgressiveLoading();
961
- this.nextProgressiveSegmentTime = 0;
962
- if (this.progressiveLoading) {
963
- this.startProgressiveLoading();
964
- }
965
- }
966
- }
967
- export default WindowedSpectrogramPlugin;
1
+ function e(e,t,s,i){return new(s||(s=Promise))((function(r,n){function o(e){try{a(i.next(e))}catch(e){n(e)}}function l(e){try{a(i.throw(e))}catch(e){n(e)}}function a(e){var t;e.done?r(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,l)}a((i=i.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{constructor(){this.listeners={}}on(e,t,s){if(this.listeners[e]||(this.listeners[e]=new Set),null==s?void 0:s.once){const s=(...i)=>{this.un(e,s),t(...i)};return this.listeners[e].add(s),()=>this.un(e,s)}return this.listeners[e].add(t),()=>this.un(e,t)}un(e,t){var s;null===(s=this.listeners[e])||void 0===s||s.delete(t)}once(e,t){return this.on(e,t,{once:!0})}unAll(){this.listeners={}}emit(e,...t){this.listeners[e]&&this.listeners[e].forEach((e=>e(...t)))}}class s extends t{constructor(e){super(),this.subscriptions=[],this.isDestroyed=!1,this.options=e}onInit(){}_init(e){this.isDestroyed&&(this.subscriptions=[],this.isDestroyed=!1),this.wavesurfer=e,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((e=>e())),this.subscriptions=[],this.isDestroyed=!0,this.wavesurfer=void 0}}function i(e,t){const s=t.xmlns?document.createElementNS(t.xmlns,e):document.createElement(e);for(const[e,r]of Object.entries(t))if("children"===e&&r)for(const[e,t]of Object.entries(r))t instanceof Node?s.appendChild(t):"string"==typeof t?s.appendChild(document.createTextNode(t)):s.appendChild(i(e,t));else"style"===e?Object.assign(s.style,r):"textContent"===e?s.textContent=r:s.setAttribute(e,r.toString());return s}function r(e,t,s){const r=i(e,t||{});return null==s||s.appendChild(r),r}const n=1e3*Math.log(10)/107.939;function o(e){return 2595*Math.log10(1+e/700)}function l(e){return 700*(Math.pow(10,e/2595)-1)}function a(e){return Math.log10(Math.max(1,e))}function h(e){return Math.pow(10,e)}function c(e){let t=26.81*e/(1960+e)-.53;return t<2&&(t+=.15*(2-t)),t>20.1&&(t+=.22*(t-20.1)),t}function d(e){return e<2&&(e=(e-.3)/.85),e>20.1&&(e=(e+4.422)/1.22),(e+.53)/(26.28-e)*1960}function u(e){return n*Math.log10(1+.00437*e)}function f(e){return(Math.pow(10,e/n)-1)/.00437}function m(e,t){switch(t){case"mel":return o(e);case"logarithmic":return a(e);case"bark":return c(e);case"erb":return u(e);default:return e}}function p(e,t,s,i,r){const n=i(0),o=i(s/2),l=Array.from({length:e},(()=>Array(t/2+1).fill(0))),a=s/t;for(let t=0;t<e;t++){const s=r(n+t/e*(o-n)),i=Math.floor(s/a),h=i*a,c=(s-h)/((i+1)*a-h);l[t][i]=1-c,l[t][i+1]=c}return l}function g(e,t){const s=t.length,i=Float32Array.from({length:s},(()=>0));for(let r=0;r<s;r++)for(let s=0;s<e.length;s++)i[r]+=e[s]*t[r][s];return i}const b={gray:()=>{const e=[];for(let t=0;t<256;t++){const s=(255-t)/256;e.push([s,s,s,1])}return e},igray:()=>{const e=[];for(let t=0;t<256;t++){const s=t/256;e.push([s,s,s,1])}return e},roseus:()=>[[.004528,.004341,.004307,1],[.005625,.006156,.00601,1],[.006628,.008293,.008161,1],[.007551,.010738,.01079,1],[.008382,.013482,.013941,1],[.009111,.01652,.017662,1],[.009727,.019846,.022009,1],[.010223,.023452,.027035,1],[.010593,.027331,.032799,1],[.010833,.031475,.039361,1],[.010941,.035875,.046415,1],[.010918,.04052,.053597,1],[.010768,.045158,.060914,1],[.010492,.049708,.068367,1],[.010098,.054171,.075954,1],[.009594,.058549,.083672,1],[.008989,.06284,.091521,1],[.008297,.067046,.099499,1],[.00753,.071165,.107603,1],[.006704,.075196,.11583,1],[.005838,.07914,.124178,1],[.004949,.082994,.132643,1],[.004062,.086758,.141223,1],[.003198,.09043,.149913,1],[.002382,.09401,.158711,1],[.001643,.097494,.167612,1],[.001009,.100883,.176612,1],[514e-6,.104174,.185704,1],[187e-6,.107366,.194886,1],[66e-6,.110457,.204151,1],[186e-6,.113445,.213496,1],[587e-6,.116329,.222914,1],[.001309,.119106,.232397,1],[.002394,.121776,.241942,1],[.003886,.124336,.251542,1],[.005831,.126784,.261189,1],[.008276,.12912,.270876,1],[.011268,.131342,.280598,1],[.014859,.133447,.290345,1],[.0191,.135435,.300111,1],[.024043,.137305,.309888,1],[.029742,.139054,.319669,1],[.036252,.140683,.329441,1],[.043507,.142189,.339203,1],[.050922,.143571,.348942,1],[.058432,.144831,.358649,1],[.066041,.145965,.368319,1],[.073744,.146974,.377938,1],[.081541,.147858,.387501,1],[.089431,.148616,.396998,1],[.097411,.149248,.406419,1],[.105479,.149754,.415755,1],[.113634,.150134,.424998,1],[.121873,.150389,.434139,1],[.130192,.150521,.443167,1],[.138591,.150528,.452075,1],[.147065,.150413,.460852,1],[.155614,.150175,.469493,1],[.164232,.149818,.477985,1],[.172917,.149343,.486322,1],[.181666,.148751,.494494,1],[.190476,.148046,.502493,1],[.199344,.147229,.510313,1],[.208267,.146302,.517944,1],[.217242,.145267,.52538,1],[.226264,.144131,.532613,1],[.235331,.142894,.539635,1],[.24444,.141559,.546442,1],[.253587,.140131,.553026,1],[.262769,.138615,.559381,1],[.271981,.137016,.5655,1],[.281222,.135335,.571381,1],[.290487,.133581,.577017,1],[.299774,.131757,.582404,1],[.30908,.129867,.587538,1],[.318399,.12792,.592415,1],[.32773,.125921,.597032,1],[.337069,.123877,.601385,1],[.346413,.121793,.605474,1],[.355758,.119678,.609295,1],[.365102,.11754,.612846,1],[.374443,.115386,.616127,1],[.383774,.113226,.619138,1],[.393096,.111066,.621876,1],[.402404,.108918,.624343,1],[.411694,.106794,.62654,1],[.420967,.104698,.628466,1],[.430217,.102645,.630123,1],[.439442,.100647,.631513,1],[.448637,.098717,.632638,1],[.457805,.096861,.633499,1],[.46694,.095095,.6341,1],[.47604,.093433,.634443,1],[.485102,.091885,.634532,1],[.494125,.090466,.63437,1],[.503104,.08919,.633962,1],[.512041,.088067,.633311,1],[.520931,.087108,.63242,1],[.529773,.086329,.631297,1],[.538564,.085738,.629944,1],[.547302,.085346,.628367,1],[.555986,.085162,.626572,1],[.564615,.08519,.624563,1],[.573187,.085439,.622345,1],[.581698,.085913,.619926,1],[.590149,.086615,.617311,1],[.598538,.087543,.614503,1],[.606862,.0887,.611511,1],[.61512,.090084,.608343,1],[.623312,.09169,.605001,1],[.631438,.093511,.601489,1],[.639492,.095546,.597821,1],[.647476,.097787,.593999,1],[.655389,.100226,.590028,1],[.66323,.102856,.585914,1],[.670995,.105669,.581667,1],[.678686,.108658,.577291,1],[.686302,.111813,.57279,1],[.69384,.115129,.568175,1],[.7013,.118597,.563449,1],[.708682,.122209,.558616,1],[.715984,.125959,.553687,1],[.723206,.12984,.548666,1],[.730346,.133846,.543558,1],[.737406,.13797,.538366,1],[.744382,.142209,.533101,1],[.751274,.146556,.527767,1],[.758082,.151008,.522369,1],[.764805,.155559,.516912,1],[.771443,.160206,.511402,1],[.777995,.164946,.505845,1],[.784459,.169774,.500246,1],[.790836,.174689,.494607,1],[.797125,.179688,.488935,1],[.803325,.184767,.483238,1],[.809435,.189925,.477518,1],[.815455,.19516,.471781,1],[.821384,.200471,.466028,1],[.827222,.205854,.460267,1],[.832968,.211308,.454505,1],[.838621,.216834,.448738,1],[.844181,.222428,.442979,1],[.849647,.22809,.43723,1],[.855019,.233819,.431491,1],[.860295,.239613,.425771,1],[.865475,.245471,.420074,1],[.870558,.251393,.414403,1],[.875545,.25738,.408759,1],[.880433,.263427,.403152,1],[.885223,.269535,.397585,1],[.889913,.275705,.392058,1],[.894503,.281934,.386578,1],[.898993,.288222,.381152,1],[.903381,.294569,.375781,1],[.907667,.300974,.370469,1],[.911849,.307435,.365223,1],[.915928,.313953,.360048,1],[.919902,.320527,.354948,1],[.923771,.327155,.349928,1],[.927533,.333838,.344994,1],[.931188,.340576,.340149,1],[.934736,.347366,.335403,1],[.938175,.354207,.330762,1],[.941504,.361101,.326229,1],[.944723,.368045,.321814,1],[.947831,.375039,.317523,1],[.950826,.382083,.313364,1],[.953709,.389175,.309345,1],[.956478,.396314,.305477,1],[.959133,.403499,.301766,1],[.961671,.410731,.298221,1],[.964093,.418008,.294853,1],[.966399,.425327,.291676,1],[.968586,.43269,.288696,1],[.970654,.440095,.285926,1],[.972603,.44754,.28338,1],[.974431,.455025,.281067,1],[.976139,.462547,.279003,1],[.977725,.470107,.277198,1],[.979188,.477703,.275666,1],[.980529,.485332,.274422,1],[.981747,.492995,.273476,1],[.98284,.50069,.272842,1],[.983808,.508415,.272532,1],[.984653,.516168,.27256,1],[.985373,.523948,.272937,1],[.985966,.531754,.273673,1],[.986436,.539582,.274779,1],[.98678,.547434,.276264,1],[.986998,.555305,.278135,1],[.987091,.563195,.280401,1],[.987061,.5711,.283066,1],[.986907,.579019,.286137,1],[.986629,.58695,.289615,1],[.986229,.594891,.293503,1],[.985709,.602839,.297802,1],[.985069,.610792,.302512,1],[.98431,.618748,.307632,1],[.983435,.626704,.313159,1],[.982445,.634657,.319089,1],[.981341,.642606,.32542,1],[.98013,.650546,.332144,1],[.978812,.658475,.339257,1],[.977392,.666391,.346753,1],[.97587,.67429,.354625,1],[.974252,.68217,.362865,1],[.972545,.690026,.371466,1],[.97075,.697856,.380419,1],[.968873,.705658,.389718,1],[.966921,.713426,.399353,1],[.964901,.721157,.409313,1],[.962815,.728851,.419594,1],[.960677,.7365,.430181,1],[.95849,.744103,.44107,1],[.956263,.751656,.452248,1],[.954009,.759153,.463702,1],[.951732,.766595,.475429,1],[.949445,.773974,.487414,1],[.947158,.781289,.499647,1],[.944885,.788535,.512116,1],[.942634,.795709,.524811,1],[.940423,.802807,.537717,1],[.938261,.809825,.550825,1],[.936163,.81676,.564121,1],[.934146,.823608,.577591,1],[.932224,.830366,.59122,1],[.930412,.837031,.604997,1],[.928727,.843599,.618904,1],[.927187,.850066,.632926,1],[.925809,.856432,.647047,1],[.92461,.862691,.661249,1],[.923607,.868843,.675517,1],[.92282,.874884,.689832,1],[.922265,.880812,.704174,1],[.921962,.886626,.718523,1],[.92193,.892323,.732859,1],[.922183,.897903,.747163,1],[.922741,.903364,.76141,1],[.92362,.908706,.77558,1],[.924837,.913928,.789648,1],[.926405,.919031,.80359,1],[.92834,.924015,.817381,1],[.930655,.928881,.830995,1],[.93336,.933631,.844405,1],[.936466,.938267,.857583,1],[.939982,.942791,.870499,1],[.943914,.947207,.883122,1],[.948267,.951519,.895421,1],[.953044,.955732,.907359,1],[.958246,.959852,.918901,1],[.963869,.963887,.930004,1],[.969909,.967845,.940623,1],[.976355,.971737,.950704,1],[.983195,.97558,.960181,1],[.990402,.979395,.968966,1],[.99793,.983217,.97692,1]]};function w(e,t,s,i){switch(this.bufferSize=e,this.sampleRate=t,this.bandwidth=2/e*(t/2),this.sinTable=new Float32Array(e),this.cosTable=new Float32Array(e),this.windowValues=new Float32Array(e),this.reverseTable=new Uint32Array(e),this.peakBand=0,this.peak=0,s){case"bartlett":for(let t=0;t<e;t++)this.windowValues[t]=2/(e-1)*((e-1)/2-Math.abs(t-(e-1)/2));break;case"bartlettHann":for(let t=0;t<e;t++)this.windowValues[t]=.62-.48*Math.abs(t/(e-1)-.5)-.38*Math.cos(2*Math.PI*t/(e-1));break;case"blackman":i=i||.16;for(let t=0;t<e;t++)this.windowValues[t]=(1-i)/2-.5*Math.cos(2*Math.PI*t/(e-1))+i/2*Math.cos(4*Math.PI*t/(e-1));break;case"cosine":for(let t=0;t<e;t++)this.windowValues[t]=Math.cos(Math.PI*t/(e-1)-Math.PI/2);break;case"gauss":i=i||.25;for(let t=0;t<e;t++)this.windowValues[t]=Math.pow(Math.E,-.5*Math.pow((t-(e-1)/2)/(i*(e-1)/2),2));break;case"hamming":for(let t=0;t<e;t++)this.windowValues[t]=.54-.46*Math.cos(2*Math.PI*t/(e-1));break;case"hann":case void 0:for(let t=0;t<e;t++)this.windowValues[t]=.5*(1-Math.cos(2*Math.PI*t/(e-1)));break;case"lanczoz":for(let t=0;t<e;t++)this.windowValues[t]=Math.sin(Math.PI*(2*t/(e-1)-1))/(Math.PI*(2*t/(e-1)-1));break;case"rectangular":for(let t=0;t<e;t++)this.windowValues[t]=1;break;case"triangular":for(let t=0;t<e;t++)this.windowValues[t]=2/e*(e/2-Math.abs(t-(e-1)/2));break;default:throw Error("No such window function '"+s+"'")}let r=1,n=e>>1;for(;r<e;){for(let e=0;e<r;e++)this.reverseTable[e+r]=this.reverseTable[e]+n;r<<=1,n>>=1}for(let t=0;t<e;t++)this.sinTable[t]=Math.sin(-Math.PI/t),this.cosTable[t]=Math.cos(-Math.PI/t);this.calculateSpectrum=function(e){const t=this.bufferSize,s=this.cosTable,i=this.sinTable,r=this.reverseTable,n=new Float32Array(t),o=new Float32Array(t),l=2/this.bufferSize,a=Math.sqrt,h=new Float32Array(t/2);let c,d,u;const f=Math.floor(Math.log(t)/Math.LN2);if(Math.pow(2,f)!==t)throw"Invalid buffer size, must be a power of 2.";if(t!==e.length)throw"Supplied buffer is not the same size as defined FFT. FFT Size: "+t+" Buffer Size: "+e.length;let m,p,g,b,w,v,y,x,S=1;for(let s=0;s<t;s++)n[s]=e[r[s]]*this.windowValues[r[s]],o[s]=0;for(;S<t;){m=s[S],p=i[S],g=1,b=0;for(let e=0;e<S;e++){let s=e;for(;s<t;)w=s+S,v=g*n[w]-b*o[w],y=g*o[w]+b*n[w],n[w]=n[s]-v,o[w]=o[s]-y,n[s]+=v,o[s]+=y,s+=S<<1;x=g,g=x*m-b*p,b=x*p+b*m}S<<=1}for(let e=0,s=t/2;e<s;e++)c=n[e],d=o[e],u=l*a(c*c+d*d),u>this.peak&&(this.peakBand=e,this.peak=u),h[e]=u;return h}}var v=null;try{var y="undefined"!=typeof module&&"function"==typeof module.require&&module.require("worker_threads")||"function"==typeof __non_webpack_require__&&__non_webpack_require__("worker_threads")||"function"==typeof require&&require("worker_threads");v=y.Worker}catch(e){}function x(e,t,s){var i=function(e){return Buffer.from(e,"base64").toString("utf8")}(e),r=i.indexOf("\n",10)+1,n=i.substring(r)+"";return function(e){return new v(n,Object.assign({},e,{eval:!0}))}}function S(e,t,s){var i=function(e){return atob(e)}(e),r=i.indexOf("\n",10)+1,n=i.substring(r)+"",o=new Blob([n],{type:"application/javascript"});return URL.createObjectURL(o)}var M="[object process]"===Object.prototype.toString.call("undefined"!=typeof process?process:0);function L(e,t,s){return M?x(e):function(e){var t;return function(s){return t=t||S(e),new Worker(t,s)}}(e)}var Z=L("Lyogcm9sbHVwLXBsdWdpbi13ZWItd29ya2VyLWxvYWRlciAqLwohZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7Y29uc3QgdD0xZTMqTWF0aC5sb2coMTApLzEwNy45Mzk7ZnVuY3Rpb24gZSh0KXtyZXR1cm4gMjU5NSpNYXRoLmxvZzEwKDErdC83MDApfWZ1bmN0aW9uIGEodCl7cmV0dXJuIDcwMCooTWF0aC5wb3coMTAsdC8yNTk1KS0xKX1mdW5jdGlvbiByKHQpe3JldHVybiBNYXRoLmxvZzEwKE1hdGgubWF4KDEsdCkpfWZ1bmN0aW9uIHModCl7cmV0dXJuIE1hdGgucG93KDEwLHQpfWZ1bmN0aW9uIG4odCl7bGV0IGU9MjYuODEqdC8oMTk2MCt0KS0uNTM7cmV0dXJuIGU8MiYmKGUrPS4xNSooMi1lKSksZT4yMC4xJiYoZSs9LjIyKihlLTIwLjEpKSxlfWZ1bmN0aW9uIG8odCl7cmV0dXJuIHQ8MiYmKHQ9KHQtLjMpLy44NSksdD4yMC4xJiYodD0odCs0LjQyMikvMS4yMiksKHQrLjUzKS8oMjYuMjgtdCkqMTk2MH1mdW5jdGlvbiBsKGUpe3JldHVybiB0Kk1hdGgubG9nMTAoMSsuMDA0MzcqZSl9ZnVuY3Rpb24gaShlKXtyZXR1cm4oTWF0aC5wb3coMTAsZS90KS0xKS8uMDA0Mzd9ZnVuY3Rpb24gaCh0LGUsYSxyLHMpe2NvbnN0IG49cigwKSxvPXIoYS8yKSxsPUFycmF5LmZyb20oe2xlbmd0aDp0fSwoKCk9PkFycmF5KGUvMisxKS5maWxsKDApKSksaT1hL2U7Zm9yKGxldCBlPTA7ZTx0O2UrKyl7Y29uc3QgYT1zKG4rZS90KihvLW4pKSxyPU1hdGguZmxvb3IoYS9pKSxoPXIqaSxjPShhLWgpLygocisxKSppLWgpO2xbZV1bcl09MS1jLGxbZV1bcisxXT1jfXJldHVybiBsfWZ1bmN0aW9uIGModCxlKXtjb25zdCBhPWUubGVuZ3RoLHI9RmxvYXQzMkFycmF5LmZyb20oe2xlbmd0aDphfSwoKCk9PjApKTtmb3IobGV0IHM9MDtzPGE7cysrKWZvcihsZXQgYT0wO2E8dC5sZW5ndGg7YSsrKXJbc10rPXRbYV0qZVtzXVthXTtyZXR1cm4gcn1mdW5jdGlvbiB1KHQsZSxhLHIpe3N3aXRjaCh0aGlzLmJ1ZmZlclNpemU9dCx0aGlzLnNhbXBsZVJhdGU9ZSx0aGlzLmJhbmR3aWR0aD0yL3QqKGUvMiksdGhpcy5zaW5UYWJsZT1uZXcgRmxvYXQzMkFycmF5KHQpLHRoaXMuY29zVGFibGU9bmV3IEZsb2F0MzJBcnJheSh0KSx0aGlzLndpbmRvd1ZhbHVlcz1uZXcgRmxvYXQzMkFycmF5KHQpLHRoaXMucmV2ZXJzZVRhYmxlPW5ldyBVaW50MzJBcnJheSh0KSx0aGlzLnBlYWtCYW5kPTAsdGhpcy5wZWFrPTAsYSl7Y2FzZSJiYXJ0bGV0dCI6Zm9yKGxldCBlPTA7ZTx0O2UrKyl0aGlzLndpbmRvd1ZhbHVlc1tlXT0yLyh0LTEpKigodC0xKS8yLU1hdGguYWJzKGUtKHQtMSkvMikpO2JyZWFrO2Nhc2UiYmFydGxldHRIYW5uIjpmb3IobGV0IGU9MDtlPHQ7ZSsrKXRoaXMud2luZG93VmFsdWVzW2VdPS42Mi0uNDgqTWF0aC5hYnMoZS8odC0xKS0uNSktLjM4Kk1hdGguY29zKDIqTWF0aC5QSSplLyh0LTEpKTticmVhaztjYXNlImJsYWNrbWFuIjpyPXJ8fC4xNjtmb3IobGV0IGU9MDtlPHQ7ZSsrKXRoaXMud2luZG93VmFsdWVzW2VdPSgxLXIpLzItLjUqTWF0aC5jb3MoMipNYXRoLlBJKmUvKHQtMSkpK3IvMipNYXRoLmNvcyg0Kk1hdGguUEkqZS8odC0xKSk7YnJlYWs7Y2FzZSJjb3NpbmUiOmZvcihsZXQgZT0wO2U8dDtlKyspdGhpcy53aW5kb3dWYWx1ZXNbZV09TWF0aC5jb3MoTWF0aC5QSSplLyh0LTEpLU1hdGguUEkvMik7YnJlYWs7Y2FzZSJnYXVzcyI6cj1yfHwuMjU7Zm9yKGxldCBlPTA7ZTx0O2UrKyl0aGlzLndpbmRvd1ZhbHVlc1tlXT1NYXRoLnBvdyhNYXRoLkUsLS41Kk1hdGgucG93KChlLSh0LTEpLzIpLyhyKih0LTEpLzIpLDIpKTticmVhaztjYXNlImhhbW1pbmciOmZvcihsZXQgZT0wO2U8dDtlKyspdGhpcy53aW5kb3dWYWx1ZXNbZV09LjU0LS40NipNYXRoLmNvcygyKk1hdGguUEkqZS8odC0xKSk7YnJlYWs7Y2FzZSJoYW5uIjpjYXNlIHZvaWQgMDpmb3IobGV0IGU9MDtlPHQ7ZSsrKXRoaXMud2luZG93VmFsdWVzW2VdPS41KigxLU1hdGguY29zKDIqTWF0aC5QSSplLyh0LTEpKSk7YnJlYWs7Y2FzZSJsYW5jem96Ijpmb3IobGV0IGU9MDtlPHQ7ZSsrKXRoaXMud2luZG93VmFsdWVzW2VdPU1hdGguc2luKE1hdGguUEkqKDIqZS8odC0xKS0xKSkvKE1hdGguUEkqKDIqZS8odC0xKS0xKSk7YnJlYWs7Y2FzZSJyZWN0YW5ndWxhciI6Zm9yKGxldCBlPTA7ZTx0O2UrKyl0aGlzLndpbmRvd1ZhbHVlc1tlXT0xO2JyZWFrO2Nhc2UidHJpYW5ndWxhciI6Zm9yKGxldCBlPTA7ZTx0O2UrKyl0aGlzLndpbmRvd1ZhbHVlc1tlXT0yL3QqKHQvMi1NYXRoLmFicyhlLSh0LTEpLzIpKTticmVhaztkZWZhdWx0OnRocm93IEVycm9yKCJObyBzdWNoIHdpbmRvdyBmdW5jdGlvbiAnIithKyInIil9bGV0IHM9MSxuPXQ+PjE7Zm9yKDtzPHQ7KXtmb3IobGV0IHQ9MDt0PHM7dCsrKXRoaXMucmV2ZXJzZVRhYmxlW3Qrc109dGhpcy5yZXZlcnNlVGFibGVbdF0rbjtzPDw9MSxuPj49MX1mb3IobGV0IGU9MDtlPHQ7ZSsrKXRoaXMuc2luVGFibGVbZV09TWF0aC5zaW4oLU1hdGguUEkvZSksdGhpcy5jb3NUYWJsZVtlXT1NYXRoLmNvcygtTWF0aC5QSS9lKTt0aGlzLmNhbGN1bGF0ZVNwZWN0cnVtPWZ1bmN0aW9uKHQpe2NvbnN0IGU9dGhpcy5idWZmZXJTaXplLGE9dGhpcy5jb3NUYWJsZSxyPXRoaXMuc2luVGFibGUscz10aGlzLnJldmVyc2VUYWJsZSxuPW5ldyBGbG9hdDMyQXJyYXkoZSksbz1uZXcgRmxvYXQzMkFycmF5KGUpLGw9Mi90aGlzLmJ1ZmZlclNpemUsaT1NYXRoLnNxcnQsaD1uZXcgRmxvYXQzMkFycmF5KGUvMik7bGV0IGMsdSxmO2NvbnN0IE09TWF0aC5mbG9vcihNYXRoLmxvZyhlKS9NYXRoLkxOMik7aWYoTWF0aC5wb3coMixNKSE9PWUpdGhyb3ciSW52YWxpZCBidWZmZXIgc2l6ZSwgbXVzdCBiZSBhIHBvd2VyIG9mIDIuIjtpZihlIT09dC5sZW5ndGgpdGhyb3ciU3VwcGxpZWQgYnVmZmVyIGlzIG5vdCB0aGUgc2FtZSBzaXplIGFzIGRlZmluZWQgRkZULiBGRlQgU2l6ZTogIitlKyIgQnVmZmVyIFNpemU6ICIrdC5sZW5ndGg7bGV0IHcsYixkLHAsZyxtLGsseSxUPTE7Zm9yKGxldCBhPTA7YTxlO2ErKyluW2FdPXRbc1thXV0qdGhpcy53aW5kb3dWYWx1ZXNbc1thXV0sb1thXT0wO2Zvcig7VDxlOyl7dz1hW1RdLGI9cltUXSxkPTEscD0wO2ZvcihsZXQgdD0wO3Q8VDt0Kyspe2xldCBhPXQ7Zm9yKDthPGU7KWc9YStULG09ZCpuW2ddLXAqb1tnXSxrPWQqb1tnXStwKm5bZ10sbltnXT1uW2FdLW0sb1tnXT1vW2FdLWssblthXSs9bSxvW2FdKz1rLGErPVQ8PDE7eT1kLGQ9eSp3LXAqYixwPXkqYitwKnd9VDw8PTF9Zm9yKGxldCB0PTAsYT1lLzI7dDxhO3QrKyljPW5bdF0sdT1vW3RdLGY9bCppKGMqYyt1KnUpLGY+dGhpcy5wZWFrJiYodGhpcy5wZWFrQmFuZD10LHRoaXMucGVhaz1mKSxoW3RdPWY7cmV0dXJuIGh9fWxldCBmPW51bGw7c2VsZi5vbm1lc3NhZ2U9ZnVuY3Rpb24odCl7Y29uc3R7dHlwZTpNLGlkOncsYXVkaW9EYXRhOmIsb3B0aW9uczpkfT10LmRhdGE7aWYoImNhbGN1bGF0ZUZyZXF1ZW5jaWVzIj09PU0pdHJ5e2NvbnN0IHQ9ZnVuY3Rpb24odCxNKXtjb25zdHtzdGFydFRpbWU6dyxlbmRUaW1lOmIsc2FtcGxlUmF0ZTpkLGZmdFNhbXBsZXM6cCx3aW5kb3dGdW5jOmcsYWxwaGE6bSxub3ZlcmxhcDprLHNjYWxlOnksZ2FpbkRCOlQscmFuZ2VEQjpGLHNwbGl0Q2hhbm5lbHM6SX09TSxWPU1hdGguZmxvb3IodypkKSxBPU1hdGguZmxvb3IoYipkKSxQPUk/dC5sZW5ndGg6MTtmJiZmLmJ1ZmZlclNpemU9PT1wfHwoZj1uZXcgdShwLGQsZyxtfHwuMTYpKTtjb25zdCBTPWZ1bmN0aW9uKHQsYyx1LGYpe3N3aXRjaCh0KXtjYXNlIm1lbCI6cmV0dXJuIGgoYyx1LGYsZSxhKTtjYXNlImxvZ2FyaXRobWljIjpyZXR1cm4gaChjLHUsZixyLHMpO2Nhc2UiYmFyayI6cmV0dXJuIGgoYyx1LGYsbixvKTtjYXNlImVyYiI6cmV0dXJuIGgoYyx1LGYsbCxpKTtkZWZhdWx0OnJldHVybiBudWxsfX0oeSxwLzIscCxkKTtsZXQgej1rfHxNYXRoLm1heCgwLE1hdGgucm91bmQoLjUqcCkpO2NvbnN0IHY9LjUqcDt6PU1hdGgubWluKHosdik7Y29uc3QgQj1NYXRoLm1heCg2NCwuMjUqcCkscT1NYXRoLm1heChCLHAteikseD1bXTtmb3IobGV0IGU9MDtlPFA7ZSsrKXtjb25zdCBhPXRbZV0scj1bXTtmb3IobGV0IHQ9Vjt0K3A8QTt0Kz1xKXtjb25zdCBlPWEuc2xpY2UodCx0K3ApO2xldCBzPWYuY2FsY3VsYXRlU3BlY3RydW0oZSk7UyYmKHM9YyhzLFMpKTtjb25zdCBuPW5ldyBVaW50OEFycmF5KHMubGVuZ3RoKSxvPVQrRjtmb3IobGV0IHQ9MDt0PHMubGVuZ3RoO3QrKyl7Y29uc3QgZT1zW3RdPjFlLTEyP3NbdF06MWUtMTIsYT0yMCpNYXRoLmxvZzEwKGUpO25bdF09YTwtbz8wOmE+LVQ/MjU1Ok1hdGgucm91bmQoKGErVCkvRioyNTUpfXIucHVzaChuKX14LnB1c2gocil9cmV0dXJuIHh9KGIsZCksTT17dHlwZToiZnJlcXVlbmNpZXNSZXN1bHQiLGlkOncscmVzdWx0OnR9O3NlbGYucG9zdE1lc3NhZ2UoTSl9Y2F0Y2godCl7Y29uc3QgZT17dHlwZToiZnJlcXVlbmNpZXNSZXN1bHQiLGlkOncsZXJyb3I6dCBpbnN0YW5jZW9mIEVycm9yP3QubWVzc2FnZTpTdHJpbmcodCl9O3NlbGYucG9zdE1lc3NhZ2UoZSl9fX0oKTsKLy8jIHNvdXJjZU1hcHBpbmdVUkw9c3BlY3Ryb2dyYW0td29ya2VyLmpzLm1hcAoK");class W extends s{static create(e){return new W(e||{})}constructor(e){var t,s;super(e),this.segments=new Map,this.buffer=null,this.currentPosition=0,this.pixelsPerSecond=0,this.isRendering=!1,this.renderTimeout=null,this.fft=null,this.progressiveLoadTimeout=null,this.isProgressiveLoading=!1,this.nextProgressiveSegmentTime=0,this.worker=null,this.workerPromises=new Map,this.qualityUpdateTimeout=null,this._onWrapperClick=e=>{const t=this.wrapper.getBoundingClientRect(),s=(e.clientX-t.left)/t.width;this.emit("click",s)},this.container="string"==typeof e.container?document.querySelector(e.container):e.container,this.colorMap=function(e="roseus"){if(e&&"string"!=typeof e){if(e.length<256)throw new Error("Colormap must contain 256 elements");for(let t=0;t<e.length;t++)if(4!==e[t].length)throw new Error("ColorMap entries must contain 4 values");return e}const t=b[e];if(!t)throw Error("No such colormap '"+e+"'");return t()}(e.colorMap),this.fftSamples=e.fftSamples||512,this.height=e.height||200,this.noverlap=e.noverlap||null,this.windowFunc=e.windowFunc||"hann",this.alpha=e.alpha,this.frequencyMin=e.frequencyMin||0,this.frequencyMax=e.frequencyMax||0,this.gainDB=null!==(t=e.gainDB)&&void 0!==t?t:20,this.rangeDB=null!==(s=e.rangeDB)&&void 0!==s?s:80,this.scale=e.scale||"mel",this.windowSize=e.windowSize||30,this.bufferSize=e.bufferSize||5e3,this.progressiveLoading=!0===e.progressiveLoading,this.useWebWorker=!0===e.useWebWorker&&"undefined"!=typeof window,this.numMelFilters=this.fftSamples/2,this.numLogFilters=this.fftSamples/2,this.numBarkFilters=this.fftSamples/2,this.numErbFilters=this.fftSamples/2,this.createWrapper(),this.createCanvas(),this.useWebWorker&&this.initializeWorker()}initializeWorker(){if("undefined"!=typeof window&&"undefined"!=typeof Worker)try{this.worker=new Z,this.worker.onmessage=e=>{const{type:t,id:s,result:i,error:r}=e.data;if("frequenciesResult"===t){const e=this.workerPromises.get(s);e&&(this.workerPromises.delete(s),r?e.reject(new Error(r)):e.resolve(i))}},this.worker.onerror=e=>{console.warn("Spectrogram worker error, falling back to main thread:",e),this.worker=null}}catch(e){console.warn("Failed to initialize worker, falling back to main thread:",e),this.worker=null}else console.warn("Worker not available in this environment, using main thread calculation")}onInit(){this.wrapper||this.createWrapper(),this.canvasContainer||this.createCanvas(),this.container=this.wavesurfer.getWrapper(),this.container.appendChild(this.wrapper),this.wavesurfer.options.fillParent&&Object.assign(this.wrapper.style,{width:"100%",overflowX:"hidden",overflowY:"hidden"}),this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{this.updatePosition(e)}))),this.subscriptions.push(this.wavesurfer.on("scroll",(()=>{this.handleScroll()}))),this.subscriptions.push(this.wavesurfer.on("redraw",(()=>this.handleRedraw()))),this.subscriptions.push(this.wavesurfer.on("ready",(()=>{const e=this.wavesurfer.getDecodedData();e&&this.render(e)}))),this.wavesurfer.getDecodedData()&&setTimeout((()=>{this.render(this.wavesurfer.getDecodedData())}),0)}createWrapper(){var e,t;this.wrapper=r("div",{style:{display:"block",position:"relative",userSelect:"none"}}),this.options.labels&&(this.labelsEl=r("canvas",{part:"spec-labels",style:{position:"absolute",zIndex:9,width:"55px",height:"100%"}},this.wrapper)),this._onWrapperClick=(e=this.wrapper,t=this.emit.bind(this),s=>{const i=e.getBoundingClientRect(),r=s.clientX-i.left,n=i.width;t("click",r/n)}),this.wrapper.addEventListener("click",this._onWrapperClick)}createCanvas(){this.canvasContainer=r("div",{style:{position:"absolute",left:0,top:0,width:"100%",height:"100%",zIndex:4}},this.wrapper)}handleRedraw(){const e=this.pixelsPerSecond;this.pixelsPerSecond=this.getPixelsPerSecond(),e!==this.pixelsPerSecond&&this.segments.size>0&&this.updateSegmentPositions(e,this.pixelsPerSecond),this.scheduleRender()}updateSegmentPositions(e,t){for(const e of this.segments.values())if(e.startPixel=e.startTime*t,e.endPixel=e.endTime*t,e.canvas){const t=e.endPixel-e.startPixel;e.canvas.style.left=`${e.startPixel}px`,e.canvas.style.width=`${t}px`}const s=t/e;(s<.5||s>2)&&this.scheduleSegmentQualityUpdate()}scheduleSegmentQualityUpdate(){this.qualityUpdateTimeout&&clearTimeout(this.qualityUpdateTimeout),this.qualityUpdateTimeout=window.setTimeout((()=>{this.updateVisibleSegmentQuality()}),500)}updateVisibleSegmentQuality(){return e(this,void 0,void 0,(function*(){var e;if(!this.buffer)return;const t=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper();if(!t)return;const s=this.getScrollLeft(t),i=this.getViewportWidth(t),r=this.getPixelsPerSecond(),n=s/r,o=(s+i)/r,l=Array.from(this.segments.values()).filter((e=>e.startTime<o&&e.endTime>n));if(0!==l.length)for(const e of l)e.canvas&&(yield this.renderSegment(e))}))}getScrollLeft(e){var t;if(e.scrollLeft)return e.scrollLeft;if(null===(t=e.parentElement)||void 0===t?void 0:t.scrollLeft)return e.parentElement.scrollLeft;if(document.documentElement.scrollLeft)return document.documentElement.scrollLeft;if(document.body.scrollLeft)return document.body.scrollLeft;if(window.scrollX)return window.scrollX;if(window.pageXOffset)return window.pageXOffset;let s=e.parentElement;for(;s;){const e=window.getComputedStyle(s);if(("scroll"===e.overflowX||"auto"===e.overflowX)&&s.scrollLeft>0)return s.scrollLeft;s=s.parentElement}return 0}getViewportWidth(e){var t,s;const i=e.offsetWidth||e.clientWidth,r=(null===(t=e.parentElement)||void 0===t?void 0:t.offsetWidth)||(null===(s=e.parentElement)||void 0===s?void 0:s.clientWidth),n=window.innerWidth;return r&&r<i?r:Math.min(i||800,.8*n)}handleScroll(){var e,t;const s=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper();let i=0;if(null==s?void 0:s.scrollLeft)i=s.scrollLeft;else if(null===(t=null==s?void 0:s.parentElement)||void 0===t?void 0:t.scrollLeft)i=s.parentElement.scrollLeft;else if(document.documentElement.scrollLeft||document.body.scrollLeft)i=document.documentElement.scrollLeft||document.body.scrollLeft;else if(window.scrollX||window.pageXOffset)i=window.scrollX||window.pageXOffset;else if(s){let e=s.parentElement;for(;e&&0===i;){const t=window.getComputedStyle(e);if(("scroll"===t.overflowX||"auto"===t.overflowX)&&e.scrollLeft>0){i=e.scrollLeft;break}e=e.parentElement}}this.getPixelsPerSecond(),this.scheduleRender()}updatePosition(e){this.currentPosition=e,this.scheduleRender()}scheduleRender(){this.renderTimeout&&clearTimeout(this.renderTimeout),this.renderTimeout=window.setTimeout((()=>{this.renderVisibleWindow()}),16)}renderVisibleWindow(){return e(this,void 0,void 0,(function*(){var e;if(!this.isRendering&&this.buffer){this.isRendering=!0;try{const t=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper();if(!t)return;const s=this.getScrollLeft(t),i=this.getViewportWidth(t),r=this.getPixelsPerSecond(),n=(this.buffer.duration,s/r),o=(s+i)/r,l=o-n;let a=Math.min(2,.5*l);l>30&&(console.warn(`⚠️ Large visible duration: ${l.toFixed(1)}s - limiting buffer`),a=1);const h=Math.max(0,n-a),c=Math.min(this.buffer.duration,o+a);yield this.generateSegments(h,c)}finally{this.isRendering=!1}}}))}generateSegments(t,s){return e(this,void 0,void 0,(function*(){if(!this.buffer)return;const e=this.getPixelsPerSecond(),i=this.getWidth(),r=this.buffer.duration,n=this.isProgressiveLoading&&s-t<=35,o=!n&&r*e<=i&&r<=60;let l,a;if(n?(a=s-t,l=a*e):o?(l=i,a=r):(l=15e3,a=l/e),this.segments.size>0)for(const[e,t]of this.segments);const h=this.findUncoveredTimeRanges(t,s,a);if(0!==h.length){for(const t of h)for(let s=t.start;s<t.end;s+=a){const r=s,n=Math.min(s+a,t.end,this.buffer.duration),l=`${Math.floor(10*r)}_${Math.floor(10*n)}`;if(this.segments.has(l))continue;performance.now();const h=yield this.calculateFrequencies(r,n);if(performance.now(),h&&h.length>0){const t={startTime:r,endTime:n,startPixel:o?0:r*e,endPixel:o?i:n*e,frequencies:h};this.segments.set(l,t),performance.now(),yield this.renderSegment(t),performance.now(),this.emitProgress()}}this.isProgressiveLoading||this.startProgressiveLoading()}}))}findUncoveredTimeRanges(e,t,s){const i=Array.from(this.segments.values()).sort(((e,t)=>e.startTime-t.startTime)),r=[];let n=e;for(const e of i){if(n<e.startTime&&n<t){const s=Math.min(e.startTime,t);r.push({start:n,end:s})}if(n=Math.max(n,e.endTime),n>=t)break}return n<t&&r.push({start:n,end:t}),r}startProgressiveLoading(){!this.isProgressiveLoading&&this.buffer&&this.progressiveLoading&&(this.isProgressiveLoading=!0,this.nextProgressiveSegmentTime=0,this.progressiveLoadTimeout=window.setTimeout((()=>{this.progressiveLoadNextSegment()}),1e3))}progressiveLoadNextSegment(){return e(this,void 0,void 0,(function*(){if(!this.buffer||!this.isProgressiveLoading)return;const e=this.buffer.duration;if(this.nextProgressiveSegmentTime>=e)return void this._stopProgressiveLoading();const t=this.nextProgressiveSegmentTime,s=Math.min(t+30,e),i=`${Math.floor(10*t)}_${Math.floor(10*s)}`;if(!this.segments.has(i))try{yield this.generateSegments(t,s)}catch(e){return console.warn("Progressive loading failed:",e),void this._stopProgressiveLoading()}this.nextProgressiveSegmentTime=s,this.progressiveLoadTimeout=window.setTimeout((()=>{this.progressiveLoadNextSegment()}),2e3)}))}_stopProgressiveLoading(){this.isProgressiveLoading=!1,this.progressiveLoadTimeout&&(clearTimeout(this.progressiveLoadTimeout),this.progressiveLoadTimeout=null)}getLoadingProgress(){if(!this.buffer)return 0;const e=this.buffer.duration;if(0===e)return 100;if(!this.isProgressiveLoading&&0===this.segments.size)return 0;const t=Math.min(100,this.nextProgressiveSegmentTime/e*100);return!this.isProgressiveLoading&&this.nextProgressiveSegmentTime>=e?100:t}emitProgress(){const e=this.getLoadingProgress()/100;this.emit("progress",e)}calculateFrequencies(t,s){return e(this,void 0,void 0,(function*(){if(!this.buffer)return[];const e=performance.now();if(this.buffer.sampleRate,!this.options.splitChannels||this.buffer.numberOfChannels,this.worker)try{const e=yield this.calculateFrequenciesWithWorker(t,s);performance.now();return e}catch(e){console.warn("Worker calculation failed, falling back to main thread:",e)}return this.calculateFrequenciesMainThread(t,s)}))}calculateFrequenciesWithWorker(t,s){return e(this,void 0,void 0,(function*(){if(!this.buffer||!this.worker)throw new Error("Worker not available");const e=this.buffer.sampleRate,i=this.options.splitChannels?this.buffer.numberOfChannels:1;let r=this.noverlap;if(!r){const i=(s-t)*this.getPixelsPerSecond(),n=Math.floor(t*e),o=(Math.floor(s*e)-n)/i;r=Math.max(0,Math.round(this.fftSamples-o))}const n=[];for(let e=0;e<i;e++)n.push(this.buffer.getChannelData(e));const o=`${Date.now()}_${Math.random()}`,l=new Promise(((e,t)=>{this.workerPromises.set(o,{resolve:e,reject:t}),setTimeout((()=>{this.workerPromises.has(o)&&(this.workerPromises.delete(o),t(new Error("Worker timeout")))}),3e4)}));return this.worker.postMessage({type:"calculateFrequencies",id:o,audioData:n,options:{startTime:t,endTime:s,sampleRate:e,fftSamples:this.fftSamples,windowFunc:this.windowFunc,alpha:this.alpha,noverlap:r,scale:this.scale,gainDB:this.gainDB,rangeDB:this.rangeDB,splitChannels:this.options.splitChannels||!1}}),l}))}calculateFrequenciesMainThread(t,s){return e(this,void 0,void 0,(function*(){if(!this.buffer)return[];const e=this.buffer.sampleRate,i=Math.floor(t*e),r=Math.floor(s*e),n=this.options.splitChannels?this.buffer.numberOfChannels:1;this.fft||(this.fft=new w(this.fftSamples,e,this.windowFunc,this.alpha));let o=this.noverlap;if(!o){const e=(r-i)/((s-t)*this.getPixelsPerSecond());o=Math.max(0,Math.round(this.fftSamples-e))}const l=.5*this.fftSamples;o=Math.min(o,l);const a=Math.max(64,.25*this.fftSamples),h=Math.max(a,this.fftSamples-o),c=[];performance.now();for(let t=0;t<n;t++){const s=this.buffer.getChannelData(t),n=[];for(let t=i;t+this.fftSamples<r;t+=h){const i=s.slice(t,t+this.fftSamples);let r=this.fft.calculateSpectrum(i);const o=this.getFilterBank(e);o&&(r=g(r,o));const l=new Uint8Array(r.length),a=this.gainDB+this.rangeDB;for(let e=0;e<r.length;e++){const t=r[e]>1e-12?r[e]:1e-12,s=20*Math.log10(t);s<-a?l[e]=0:s>-this.gainDB?l[e]=255:l[e]=Math.round((s+this.gainDB)/this.rangeDB*255)}n.push(l)}c.push(n)}return performance.now(),c}))}renderSegment(t){return e(this,void 0,void 0,(function*(){var e;const s=t.endPixel-t.startPixel,i=this.height*t.frequencies.length,r=document.createElement("canvas");r.width=Math.round(s),r.height=Math.round(i),r.style.position="absolute",r.style.left=`${t.startPixel}px`,r.style.top="0",r.style.width=`${s}px`,r.style.height=`${i}px`;const n=r.getContext("2d");if(!n)return;const o=(null===(e=this.buffer)||void 0===e?void 0:e.sampleRate)?this.buffer.sampleRate/2:0,l=this.frequencyMin,a=this.frequencyMax||o;for(let e=0;e<t.frequencies.length;e++)yield this.renderChannelToCanvas(t.frequencies[e],n,s,this.height,e*this.height,o,l,a);t.canvas=r,this.canvasContainer.appendChild(r)}))}renderChannelToCanvas(t,s,i,r,n,o,l,a){return e(this,void 0,void 0,(function*(){if(0===t.length)return;const e=t[0].length,h=new ImageData(t.length,e),c=h.data;for(let s=0;s<t.length;s++){const i=t[s];for(let r=0;r<e;r++){const n=Math.min(255,Math.max(0,i[r])),o=this.colorMap[n],l=4*((e-r-1)*t.length+s);c[l]=255*o[0],c[l+1]=255*o[1],c[l+2]=255*o[2],c[l+3]=255*o[3]}}const d=m(l,this.scale)/m(o,this.scale),u=m(a,this.scale)/m(o,this.scale),f=Math.min(1,u),p=r*f/u,g=n+r*(1-f/u),b=Math.round(e*(1-f)),w=Math.round(e*(f-d)),v=yield createImageBitmap(h,0,b,t.length,w);s.drawImage(v,0,g,i,p),"close"in v&&v.close()}))}clearAllSegments(){for(const e of this.segments.values())e.canvas&&e.canvas.remove();this.segments.clear()}getFilterBank(e){const t=this.fftSamples/2;return function(e,t,s,i){switch(e){case"mel":return p(t,s,i,o,l);case"logarithmic":return p(t,s,i,a,h);case"bark":return p(t,s,i,c,d);case"erb":return p(t,s,i,u,f);default:return null}}(this.scale,t,this.fftSamples,e)}freqType(e){return e>=1e3?(e/1e3).toFixed(1):Math.round(e)}unitType(e){return e>=1e3?"kHz":"Hz"}getLabelFrequency(e,t){const s=m(this.frequencyMin,this.scale);return function(e,t){switch(t){case"mel":return l(e);case"logarithmic":return h(e);case"bark":return d(e);case"erb":return f(e);default:return e}}(s+e/t*(m(this.frequencyMax,this.scale)-s),this.scale)}loadLabels(e,t,s,i,r,n,o,l,a){e=e||"rgba(68,68,68,0)",t=t||"12px",s=s||"12px",i=i||"Helvetica",r=r||"#fff",n=n||"#fff",o=o||"center";const h=this.height||512,c=h/256*5;this.frequencyMin;const d=this.labelsEl.getContext("2d"),u=window.devicePixelRatio;if(this.labelsEl.height=this.height*a*u,this.labelsEl.width=55*u,d.scale(u,u),d)for(let l=0;l<a;l++){let a;for(d.fillStyle=e,d.fillRect(0,l*h,55,(1+l)*h),d.fill(),a=0;a<=c;a++){d.textAlign=o,d.textBaseline="middle";const e=this.getLabelFrequency(a,c),u=this.freqType(e),f=this.unitType(e),m=16;let p=(1+l)*h-a/c*h;p=Math.min(Math.max(p,l*h+10),(1+l)*h-10),d.fillStyle=n,d.font=s+" "+i,d.fillText(f,m+24,p),d.fillStyle=r,d.font=t+" "+i,d.fillText(u.toString(),m,p)}}}render(t){return e(this,void 0,void 0,(function*(){this.buffer=t,this.pixelsPerSecond=this.getPixelsPerSecond(),this.frequencyMax=this.frequencyMax||t.sampleRate/2;const e=this.options.splitChannels?t.numberOfChannels:1;this.wrapper.style.height=this.height*e+"px",this.clearAllSegments(),this.nextProgressiveSegmentTime=0,this.options.labels&&this.loadLabels(this.options.labelsBackground,"12px","12px","",this.options.labelsColor,this.options.labelsHzColor||this.options.labelsColor,"center","#specLabels",e),this.scheduleRender(),this.emit("ready")}))}destroy(){this.unAll(),this.renderTimeout&&(clearTimeout(this.renderTimeout),this.renderTimeout=null),this.qualityUpdateTimeout&&(clearTimeout(this.qualityUpdateTimeout),this.qualityUpdateTimeout=null),this.stopProgressiveLoading(),this.nextProgressiveSegmentTime=0,this.worker&&(this.worker.terminate(),this.worker=null);for(const[e,t]of this.workerPromises)t.reject(new Error("Plugin destroyed"));this.workerPromises.clear(),this.clearAllSegments(),this.canvasContainer&&(this.canvasContainer.remove(),this.canvasContainer=null),this.wrapper&&(this.wrapper.remove(),this.wrapper=null),this.labelsEl&&(this.labelsEl.remove(),this.labelsEl=null),this.container=null,this.buffer=null,this.fft=null,this.isRendering=!1,this.currentPosition=0,this.pixelsPerSecond=0,super.destroy()}getWidth(){var e,t;return(null===(t=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper())||void 0===t?void 0:t.offsetWidth)||0}getPixelsPerSecond(){var e;const t=null===(e=this.wavesurfer)||void 0===e?void 0:e.options.minPxPerSec;if(t&&t>0)return t;if(this.buffer){const e=this.getWidth(),t=e>0?e/this.buffer.duration:100;return Math.max(t,50)}return 50}stopProgressiveLoading(){this.isProgressiveLoading=!1,this.progressiveLoadTimeout&&(clearTimeout(this.progressiveLoadTimeout),this.progressiveLoadTimeout=null)}restartProgressiveLoading(){this.stopProgressiveLoading(),this.nextProgressiveSegmentTime=0,this.progressiveLoading&&this.startProgressiveLoading()}}export{W as default};