wavesurfer.js 7.0.0-beta.9 → 7.0.1
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.
- package/README.md +56 -24
- package/dist/base-plugin.d.ts +6 -3
- package/dist/base-plugin.js +1 -0
- package/dist/decoder.js +17 -6
- package/dist/draggable.js +10 -7
- package/dist/fetcher.d.ts +1 -3
- package/dist/fetcher.js +13 -6
- package/dist/player.d.ts +2 -2
- package/dist/player.js +10 -8
- package/dist/plugins/base-plugin.d.ts +16 -0
- package/dist/plugins/decoder.d.ts +9 -0
- package/dist/plugins/draggable.d.ts +1 -0
- package/dist/plugins/envelope.cjs +1 -0
- package/dist/plugins/envelope.d.ts +12 -4
- package/dist/plugins/envelope.esm.js +1 -0
- package/dist/plugins/envelope.js +34 -10
- package/dist/plugins/envelope.min.js +1 -0
- package/dist/plugins/event-emitter.d.ts +19 -0
- package/dist/plugins/fetcher.d.ts +5 -0
- package/dist/plugins/hover.cjs +1 -0
- package/dist/plugins/hover.d.ts +35 -0
- package/dist/plugins/hover.esm.js +1 -0
- package/dist/plugins/hover.js +101 -0
- package/dist/plugins/hover.min.js +1 -0
- package/dist/plugins/minimap.cjs +1 -0
- package/dist/plugins/minimap.d.ts +2 -2
- package/dist/plugins/minimap.esm.js +1 -0
- package/dist/plugins/minimap.js +8 -13
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/player.d.ts +45 -0
- package/dist/plugins/plugins/envelope.d.ts +79 -0
- package/dist/plugins/plugins/hover.d.ts +35 -0
- package/dist/plugins/plugins/minimap.d.ts +39 -0
- package/dist/plugins/plugins/record.d.ts +31 -0
- package/dist/plugins/plugins/regions.d.ts +118 -0
- package/dist/plugins/plugins/spectrogram.d.ts +76 -0
- package/dist/plugins/plugins/timeline.d.ts +47 -0
- package/dist/plugins/record.cjs +1 -0
- package/dist/plugins/record.d.ts +7 -4
- package/dist/plugins/record.esm.js +1 -0
- package/dist/plugins/record.js +73 -66
- package/dist/plugins/record.min.js +1 -0
- package/dist/plugins/regions.cjs +1 -0
- package/dist/plugins/regions.d.ts +28 -4
- package/dist/plugins/regions.esm.js +1 -0
- package/dist/plugins/regions.js +74 -61
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/renderer.d.ts +44 -0
- package/dist/plugins/spectrogram.cjs +1 -0
- package/dist/plugins/spectrogram.d.ts +10 -3
- package/dist/plugins/spectrogram.esm.js +1 -0
- package/dist/plugins/spectrogram.js +166 -20
- package/dist/plugins/spectrogram.min.js +1 -0
- package/dist/plugins/timeline.cjs +1 -0
- package/dist/plugins/timeline.d.ts +5 -3
- package/dist/plugins/timeline.esm.js +1 -0
- package/dist/plugins/timeline.js +27 -21
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/plugins/timer.d.ts +11 -0
- package/dist/plugins/wavesurfer.d.ts +156 -0
- package/dist/renderer.js +11 -7
- package/dist/wavesurfer.cjs +1 -0
- package/dist/wavesurfer.d.ts +5 -3
- package/dist/wavesurfer.esm.js +1 -0
- package/dist/wavesurfer.js +62 -41
- package/dist/wavesurfer.min.js +1 -0
- package/package.json +21 -22
- package/dist/plugins/envelope.min.cjs +0 -1
- package/dist/plugins/minimap.min.cjs +0 -1
- package/dist/plugins/record.min.cjs +0 -1
- package/dist/plugins/regions.min.cjs +0 -1
- package/dist/plugins/spectrogram-fft.d.ts +0 -9
- package/dist/plugins/spectrogram-fft.js +0 -150
- package/dist/plugins/spectrogram.min.cjs +0 -1
- package/dist/plugins/timeline.min.cjs +0 -1
- package/dist/wavesurfer.min.cjs +0 -1
package/dist/plugins/regions.js
CHANGED
|
@@ -6,16 +6,21 @@
|
|
|
6
6
|
import BasePlugin from '../base-plugin.js';
|
|
7
7
|
import { makeDraggable } from '../draggable.js';
|
|
8
8
|
import EventEmitter from '../event-emitter.js';
|
|
9
|
-
|
|
9
|
+
class SingleRegion extends EventEmitter {
|
|
10
10
|
constructor(params, totalDuration) {
|
|
11
|
+
var _a, _b, _c, _d, _e, _f;
|
|
11
12
|
super();
|
|
12
13
|
this.totalDuration = totalDuration;
|
|
14
|
+
this.minLength = 0;
|
|
15
|
+
this.maxLength = Infinity;
|
|
13
16
|
this.id = params.id || `region-${Math.random().toString(32).slice(2)}`;
|
|
14
17
|
this.start = params.start;
|
|
15
|
-
this.end = params.end
|
|
16
|
-
this.drag = params.drag
|
|
17
|
-
this.resize = params.resize
|
|
18
|
-
this.color = params.color
|
|
18
|
+
this.end = (_a = params.end) !== null && _a !== void 0 ? _a : params.start;
|
|
19
|
+
this.drag = (_b = params.drag) !== null && _b !== void 0 ? _b : true;
|
|
20
|
+
this.resize = (_c = params.resize) !== null && _c !== void 0 ? _c : true;
|
|
21
|
+
this.color = (_d = params.color) !== null && _d !== void 0 ? _d : 'rgba(0, 0, 0, 0.1)';
|
|
22
|
+
this.minLength = (_e = params.minLength) !== null && _e !== void 0 ? _e : this.minLength;
|
|
23
|
+
this.maxLength = (_f = params.maxLength) !== null && _f !== void 0 ? _f : this.maxLength;
|
|
19
24
|
this.element = this.initElement(params.content);
|
|
20
25
|
this.renderPosition();
|
|
21
26
|
this.initMouseEvents();
|
|
@@ -34,13 +39,12 @@ export class Region extends EventEmitter {
|
|
|
34
39
|
transition: background-color 0.2s ease;
|
|
35
40
|
cursor: ${this.drag ? 'grab' : 'default'};
|
|
36
41
|
pointer-events: all;
|
|
37
|
-
padding: 0.2em ${isMarker ? 0.2 : 0.4}em;
|
|
38
|
-
pointer-events: all;
|
|
39
42
|
`);
|
|
40
43
|
// Init content
|
|
41
44
|
if (content) {
|
|
42
45
|
if (typeof content === 'string') {
|
|
43
46
|
this.content = document.createElement('div');
|
|
47
|
+
this.content.style.padding = `0.2em ${isMarker ? 0.2 : 0.4}em`;
|
|
44
48
|
this.content.textContent = content;
|
|
45
49
|
}
|
|
46
50
|
else {
|
|
@@ -81,9 +85,9 @@ export class Region extends EventEmitter {
|
|
|
81
85
|
}
|
|
82
86
|
renderPosition() {
|
|
83
87
|
const start = this.start / this.totalDuration;
|
|
84
|
-
const end = this.end / this.totalDuration;
|
|
88
|
+
const end = (this.totalDuration - this.end) / this.totalDuration;
|
|
85
89
|
this.element.style.left = `${start * 100}%`;
|
|
86
|
-
this.element.style.
|
|
90
|
+
this.element.style.right = `${end * 100}%`;
|
|
87
91
|
}
|
|
88
92
|
initMouseEvents() {
|
|
89
93
|
const { element } = this;
|
|
@@ -111,31 +115,33 @@ export class Region extends EventEmitter {
|
|
|
111
115
|
this.element.style.cursor = 'grab';
|
|
112
116
|
this.emit('update-end');
|
|
113
117
|
}
|
|
114
|
-
|
|
118
|
+
_onUpdate(dx, side) {
|
|
115
119
|
if (!this.element.parentElement)
|
|
116
120
|
return;
|
|
117
121
|
const deltaSeconds = (dx / this.element.parentElement.clientWidth) * this.totalDuration;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
const newStart = !side || side === 'start' ? this.start + deltaSeconds : this.start;
|
|
123
|
+
const newEnd = !side || side === 'end' ? this.end + deltaSeconds : this.end;
|
|
124
|
+
const length = newEnd - newStart;
|
|
125
|
+
if (newStart >= 0 &&
|
|
126
|
+
newEnd <= this.totalDuration &&
|
|
127
|
+
newStart <= newEnd &&
|
|
128
|
+
length >= this.minLength &&
|
|
129
|
+
length <= this.maxLength) {
|
|
130
|
+
this.start = newStart;
|
|
131
|
+
this.end = newEnd;
|
|
132
|
+
this.renderPosition();
|
|
133
|
+
this.emit('update');
|
|
134
|
+
}
|
|
129
135
|
}
|
|
130
136
|
onMove(dx) {
|
|
131
137
|
if (!this.drag)
|
|
132
138
|
return;
|
|
133
|
-
this.
|
|
139
|
+
this._onUpdate(dx);
|
|
134
140
|
}
|
|
135
141
|
onResize(dx, side) {
|
|
136
142
|
if (!this.resize)
|
|
137
143
|
return;
|
|
138
|
-
this.
|
|
144
|
+
this._onUpdate(dx, side);
|
|
139
145
|
}
|
|
140
146
|
onEndResizing() {
|
|
141
147
|
if (!this.resize)
|
|
@@ -152,6 +158,7 @@ export class Region extends EventEmitter {
|
|
|
152
158
|
}
|
|
153
159
|
/** Update the region's options */
|
|
154
160
|
setOptions(options) {
|
|
161
|
+
var _a, _b;
|
|
155
162
|
if (options.color) {
|
|
156
163
|
this.color = options.color;
|
|
157
164
|
this.element.style.backgroundColor = this.color;
|
|
@@ -168,8 +175,8 @@ export class Region extends EventEmitter {
|
|
|
168
175
|
});
|
|
169
176
|
}
|
|
170
177
|
if (options.start !== undefined || options.end !== undefined) {
|
|
171
|
-
this.start = options.start
|
|
172
|
-
this.end = options.end
|
|
178
|
+
this.start = (_a = options.start) !== null && _a !== void 0 ? _a : this.start;
|
|
179
|
+
this.end = (_b = options.end) !== null && _b !== void 0 ? _b : this.end;
|
|
173
180
|
this.renderPosition();
|
|
174
181
|
}
|
|
175
182
|
}
|
|
@@ -199,6 +206,19 @@ class RegionsPlugin extends BasePlugin {
|
|
|
199
206
|
throw Error('WaveSurfer is not initialized');
|
|
200
207
|
}
|
|
201
208
|
this.wavesurfer.getWrapper().appendChild(this.regionsContainer);
|
|
209
|
+
// Detect when a region is being played
|
|
210
|
+
let activeRegion = null;
|
|
211
|
+
this.subscriptions.push(this.wavesurfer.on('timeupdate', (currentTime) => {
|
|
212
|
+
const playedRegion = this.regions.find((region) => region.start <= currentTime && region.end >= currentTime);
|
|
213
|
+
if (activeRegion && activeRegion !== playedRegion) {
|
|
214
|
+
this.emit('region-out', activeRegion);
|
|
215
|
+
activeRegion = null;
|
|
216
|
+
}
|
|
217
|
+
if (playedRegion && playedRegion !== activeRegion) {
|
|
218
|
+
activeRegion = playedRegion;
|
|
219
|
+
this.emit('region-in', playedRegion);
|
|
220
|
+
}
|
|
221
|
+
}));
|
|
202
222
|
}
|
|
203
223
|
initRegionsContainer() {
|
|
204
224
|
const div = document.createElement('div');
|
|
@@ -233,7 +253,7 @@ class RegionsPlugin extends BasePlugin {
|
|
|
233
253
|
const width = reg.element.scrollWidth;
|
|
234
254
|
return labelLeft < left + width && left < labelLeft + labelWidth;
|
|
235
255
|
})
|
|
236
|
-
.map((reg) => reg.content
|
|
256
|
+
.map((reg) => { var _a; return ((_a = reg.content) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().height) || 0; })
|
|
237
257
|
.reduce((sum, val) => sum + val, 0);
|
|
238
258
|
div.style.marginTop = `${overlap}px`;
|
|
239
259
|
}
|
|
@@ -248,8 +268,9 @@ class RegionsPlugin extends BasePlugin {
|
|
|
248
268
|
this.emit('region-updated', region);
|
|
249
269
|
}),
|
|
250
270
|
region.on('play', () => {
|
|
251
|
-
|
|
252
|
-
this.wavesurfer
|
|
271
|
+
var _a, _b;
|
|
272
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.play();
|
|
273
|
+
(_b = this.wavesurfer) === null || _b === void 0 ? void 0 : _b.setTime(region.start);
|
|
253
274
|
}),
|
|
254
275
|
region.on('click', (e) => {
|
|
255
276
|
this.emit('region-clicked', region, e);
|
|
@@ -271,7 +292,7 @@ class RegionsPlugin extends BasePlugin {
|
|
|
271
292
|
throw Error('WaveSurfer is not initialized');
|
|
272
293
|
}
|
|
273
294
|
const duration = this.wavesurfer.getDuration();
|
|
274
|
-
const region = new
|
|
295
|
+
const region = new SingleRegion(options, duration);
|
|
275
296
|
if (!duration) {
|
|
276
297
|
this.subscriptions.push(this.wavesurfer.once('ready', (duration) => {
|
|
277
298
|
region._setTotalDuration(duration);
|
|
@@ -288,52 +309,44 @@ class RegionsPlugin extends BasePlugin {
|
|
|
288
309
|
* Returns a function to disable the drag selection.
|
|
289
310
|
*/
|
|
290
311
|
enableDragSelection(options) {
|
|
291
|
-
|
|
312
|
+
var _a, _b;
|
|
313
|
+
const wrapper = (_b = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper()) === null || _b === void 0 ? void 0 : _b.querySelector('div');
|
|
292
314
|
if (!wrapper)
|
|
293
315
|
return () => undefined;
|
|
316
|
+
const initialSize = 5;
|
|
294
317
|
let region = null;
|
|
295
|
-
let
|
|
318
|
+
let startX = 0;
|
|
296
319
|
return makeDraggable(wrapper,
|
|
297
320
|
// On drag move
|
|
298
|
-
(dx,
|
|
299
|
-
if (!this.wavesurfer)
|
|
300
|
-
return;
|
|
301
|
-
if (!region) {
|
|
302
|
-
const duration = this.wavesurfer.getDuration();
|
|
303
|
-
const box = wrapper.getBoundingClientRect();
|
|
304
|
-
let start = (x / box.width) * duration;
|
|
305
|
-
let end = ((x - box.left) / box.width) * duration;
|
|
306
|
-
if (start > end)
|
|
307
|
-
[start, end] = [end, start];
|
|
308
|
-
region = new Region({
|
|
309
|
-
...options,
|
|
310
|
-
start,
|
|
311
|
-
end,
|
|
312
|
-
}, duration);
|
|
313
|
-
this.regionsContainer.appendChild(region.element);
|
|
314
|
-
}
|
|
315
|
-
sumDx += dx;
|
|
321
|
+
(dx, _dy, x) => {
|
|
316
322
|
if (region) {
|
|
317
|
-
|
|
318
|
-
|
|
323
|
+
// Update the end position of the region
|
|
324
|
+
// If we're dragging to the left, we need to update the start instead
|
|
325
|
+
region._onUpdate(dx, x > startX ? 'end' : 'start');
|
|
319
326
|
}
|
|
320
327
|
},
|
|
321
328
|
// On drag start
|
|
322
|
-
() =>
|
|
329
|
+
(x) => {
|
|
330
|
+
startX = x;
|
|
331
|
+
if (!this.wavesurfer)
|
|
332
|
+
return;
|
|
333
|
+
const duration = this.wavesurfer.getDuration();
|
|
334
|
+
const width = this.wavesurfer.getWrapper().clientWidth;
|
|
335
|
+
// Calculate the start time of the region
|
|
336
|
+
const start = (x / width) * duration;
|
|
337
|
+
// Give the region a small initial size
|
|
338
|
+
const end = ((x + initialSize) / width) * duration;
|
|
339
|
+
// Create a region but don't save it until the drag ends
|
|
340
|
+
region = new SingleRegion(Object.assign(Object.assign({}, options), { start,
|
|
341
|
+
end }), duration);
|
|
342
|
+
// Just add it to the DOM for now
|
|
343
|
+
this.regionsContainer.appendChild(region.element);
|
|
344
|
+
},
|
|
323
345
|
// On drag end
|
|
324
346
|
() => {
|
|
325
347
|
if (region) {
|
|
326
348
|
this.saveRegion(region);
|
|
327
349
|
region = null;
|
|
328
|
-
sumDx = 0;
|
|
329
|
-
// Prevent a click event on the waveform
|
|
330
|
-
if (this.wavesurfer) {
|
|
331
|
-
const { interact } = this.wavesurfer.options;
|
|
332
|
-
if (interact) {
|
|
333
|
-
this.wavesurfer.toggleInteraction(false);
|
|
334
|
-
setTimeout(() => this.wavesurfer?.toggleInteraction(interact), 10);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
350
|
}
|
|
338
351
|
});
|
|
339
352
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):((t="undefined"!=typeof globalThis?globalThis:t||self).WaveSurfer=t.WaveSurfer||{},t.WaveSurfer.Regions=e())}(this,(function(){"use strict";class t{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 i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}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)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}function i(t,e,i,n,s=5){let r=()=>{};if(!t)return r;const o=o=>{if(2===o.button)return;o.preventDefault(),o.stopPropagation();let l=o.clientX,a=o.clientY,h=!1;const d=n=>{n.preventDefault(),n.stopPropagation();const r=n.clientX,o=n.clientY;if(h||Math.abs(r-l)>=s||Math.abs(o-a)>=s){const{left:n,top:s}=t.getBoundingClientRect();h||(h=!0,null==i||i(l-n,a-s)),e(r-l,o-a,r-n,o-s),l=r,a=o}},u=t=>{h&&(t.preventDefault(),t.stopPropagation())},c=()=>{h&&(null==n||n()),r()};document.addEventListener("pointermove",d),document.addEventListener("pointerup",c),document.addEventListener("pointerleave",c),document.addEventListener("click",u,!0),r=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",c),document.removeEventListener("pointerleave",c),setTimeout((()=>{document.removeEventListener("click",u,!0)}),10)}};return t.addEventListener("pointerdown",o),()=>{r(),t.removeEventListener("pointerdown",o)}}class n extends t{constructor(t,e){var i,n,s,r,o,l;super(),this.totalDuration=e,this.minLength=0,this.maxLength=1/0,this.id=t.id||`region-${Math.random().toString(32).slice(2)}`,this.start=t.start,this.end=null!==(i=t.end)&&void 0!==i?i:t.start,this.drag=null===(n=t.drag)||void 0===n||n,this.resize=null===(s=t.resize)||void 0===s||s,this.color=null!==(r=t.color)&&void 0!==r?r:"rgba(0, 0, 0, 0.1)",this.minLength=null!==(o=t.minLength)&&void 0!==o?o:this.minLength,this.maxLength=null!==(l=t.maxLength)&&void 0!==l?l:this.maxLength,this.element=this.initElement(t.content),this.renderPosition(),this.initMouseEvents()}initElement(t){const e=document.createElement("div"),i=this.start===this.end;if(e.setAttribute("part",`${i?"marker":"region"} ${this.id}`),e.setAttribute("style",`\n position: absolute;\n height: 100%;\n background-color: ${i?"none":this.color};\n border-left: ${i?"2px solid "+this.color:"none"};\n border-radius: 2px;\n box-sizing: border-box;\n transition: background-color 0.2s ease;\n cursor: ${this.drag?"grab":"default"};\n pointer-events: all;\n `),t&&("string"==typeof t?(this.content=document.createElement("div"),this.content.style.padding=`0.2em ${i?.2:.4}em`,this.content.textContent=t):this.content=t,this.content.setAttribute("part","region-content"),e.appendChild(this.content)),!i){const t=document.createElement("div");t.setAttribute("data-resize","left"),t.setAttribute("style",`\n position: absolute;\n z-index: 2;\n width: 6px;\n height: 100%;\n top: 0;\n left: 0;\n border-left: 2px solid rgba(0, 0, 0, 0.5);\n border-radius: 2px 0 0 2px;\n cursor: ${this.resize?"ew-resize":"default"};\n word-break: keep-all;\n `),t.setAttribute("part","region-handle region-handle-left");const i=t.cloneNode();i.setAttribute("data-resize","right"),i.style.left="",i.style.right="0",i.style.borderRight=i.style.borderLeft,i.style.borderLeft="",i.style.borderRadius="0 2px 2px 0",i.setAttribute("part","region-handle region-handle-right"),e.appendChild(t),e.appendChild(i)}return e}renderPosition(){const t=this.start/this.totalDuration,e=(this.totalDuration-this.end)/this.totalDuration;this.element.style.left=100*t+"%",this.element.style.right=100*e+"%"}initMouseEvents(){const{element:t}=this;if(!t)return;t.addEventListener("click",(t=>this.emit("click",t))),t.addEventListener("mouseenter",(t=>this.emit("over",t))),t.addEventListener("mouseleave",(t=>this.emit("leave",t))),t.addEventListener("dblclick",(t=>this.emit("dblclick",t))),i(t,(t=>this.onMove(t)),(()=>this.onStartMoving()),(()=>this.onEndMoving()));i(t.querySelector('[data-resize="left"]'),(t=>this.onResize(t,"start")),(()=>null),(()=>this.onEndResizing()),1),i(t.querySelector('[data-resize="right"]'),(t=>this.onResize(t,"end")),(()=>null),(()=>this.onEndResizing()),1)}onStartMoving(){this.drag&&(this.element.style.cursor="grabbing")}onEndMoving(){this.drag&&(this.element.style.cursor="grab",this.emit("update-end"))}_onUpdate(t,e){if(!this.element.parentElement)return;const i=t/this.element.parentElement.clientWidth*this.totalDuration,n=e&&"start"!==e?this.start:this.start+i,s=e&&"end"!==e?this.end:this.end+i,r=s-n;n>=0&&s<=this.totalDuration&&n<=s&&r>=this.minLength&&r<=this.maxLength&&(this.start=n,this.end=s,this.renderPosition(),this.emit("update"))}onMove(t){this.drag&&this._onUpdate(t)}onResize(t,e){this.resize&&this._onUpdate(t,e)}onEndResizing(){this.resize&&this.emit("update-end")}_setTotalDuration(t){this.totalDuration=t,this.renderPosition()}play(){this.emit("play")}setOptions(t){var e,i;t.color&&(this.color=t.color,this.element.style.backgroundColor=this.color),void 0!==t.drag&&(this.drag=t.drag,this.element.style.cursor=this.drag?"grab":"default"),void 0!==t.resize&&(this.resize=t.resize,this.element.querySelectorAll("[data-resize]").forEach((t=>{t.style.cursor=this.resize?"ew-resize":"default"}))),void 0===t.start&&void 0===t.end||(this.start=null!==(e=t.start)&&void 0!==e?e:this.start,this.end=null!==(i=t.end)&&void 0!==i?i:this.end,this.renderPosition())}remove(){this.emit("remove"),this.element.remove(),this.element=null}}class s extends e{constructor(t){super(t),this.regions=[],this.regionsContainer=this.initRegionsContainer()}static create(t){return new s(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer);let t=null;this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{const i=this.regions.find((t=>t.start<=e&&t.end>=e));t&&t!==i&&(this.emit("region-out",t),t=null),i&&i!==t&&(t=i,this.emit("region-in",i))})))}initRegionsContainer(){const t=document.createElement("div");return t.setAttribute("style","\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 3;\n pointer-events: none;\n "),t}getRegions(){return this.regions}avoidOverlapping(t){if(!t.content)return;const e=t.content,i=e.getBoundingClientRect().left,n=t.element.scrollWidth,s=this.regions.filter((e=>{if(e===t||!e.content)return!1;const s=e.content.getBoundingClientRect().left,r=e.element.scrollWidth;return i<s+r&&s<i+n})).map((t=>{var e;return(null===(e=t.content)||void 0===e?void 0:e.getBoundingClientRect().height)||0})).reduce(((t,e)=>t+e),0);e.style.marginTop=`${s}px`}saveRegion(t){this.regionsContainer.appendChild(t.element),this.avoidOverlapping(t),this.regions.push(t),this.emit("region-created",t);const e=[t.on("update-end",(()=>{this.avoidOverlapping(t),this.emit("region-updated",t)})),t.on("play",(()=>{var e,i;null===(e=this.wavesurfer)||void 0===e||e.play(),null===(i=this.wavesurfer)||void 0===i||i.setTime(t.start)})),t.on("click",(e=>{this.emit("region-clicked",t,e)})),t.on("dblclick",(e=>{this.emit("region-double-clicked",t,e)})),t.once("remove",(()=>{e.forEach((t=>t())),this.regions=this.regions.filter((e=>e!==t))}))];this.subscriptions.push(...e)}addRegion(t){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const e=this.wavesurfer.getDuration(),i=new n(t,e);return e?this.saveRegion(i):this.subscriptions.push(this.wavesurfer.once("ready",(t=>{i._setTotalDuration(t),this.saveRegion(i)}))),i}enableDragSelection(t){var e,s;const r=null===(s=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper())||void 0===s?void 0:s.querySelector("div");if(!r)return()=>{};let o=null,l=0;return i(r,((t,e,i)=>{o&&o._onUpdate(t,i>l?"end":"start")}),(e=>{if(l=e,!this.wavesurfer)return;const i=this.wavesurfer.getDuration(),s=this.wavesurfer.getWrapper().clientWidth,r=e/s*i,a=(e+5)/s*i;o=new n(Object.assign(Object.assign({},t),{start:r,end:a}),i),this.regionsContainer.appendChild(o.element)}),(()=>{o&&(this.saveRegion(o),o=null)}))}clearRegions(){this.regions.forEach((t=>t.remove()))}destroy(){this.clearRegions(),super.destroy()}}return s}));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
import type { WaveSurferOptions } from './wavesurfer.js';
|
|
3
|
+
type RendererEvents = {
|
|
4
|
+
click: [relativeX: number];
|
|
5
|
+
drag: [relativeX: number];
|
|
6
|
+
scroll: [relativeStart: number, relativeEnd: number];
|
|
7
|
+
render: [];
|
|
8
|
+
};
|
|
9
|
+
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
10
|
+
private static MAX_CANVAS_WIDTH;
|
|
11
|
+
private options;
|
|
12
|
+
private parent;
|
|
13
|
+
private container;
|
|
14
|
+
private scrollContainer;
|
|
15
|
+
private wrapper;
|
|
16
|
+
private canvasWrapper;
|
|
17
|
+
private progressWrapper;
|
|
18
|
+
private cursor;
|
|
19
|
+
private timeouts;
|
|
20
|
+
private isScrolling;
|
|
21
|
+
private audioData;
|
|
22
|
+
private resizeObserver;
|
|
23
|
+
private isDragging;
|
|
24
|
+
constructor(options: WaveSurferOptions);
|
|
25
|
+
private initEvents;
|
|
26
|
+
private initDrag;
|
|
27
|
+
private getHeight;
|
|
28
|
+
private initHtml;
|
|
29
|
+
setOptions(options: WaveSurferOptions): void;
|
|
30
|
+
getWrapper(): HTMLElement;
|
|
31
|
+
getScroll(): number;
|
|
32
|
+
destroy(): void;
|
|
33
|
+
private createDelay;
|
|
34
|
+
private convertColorValues;
|
|
35
|
+
private renderBars;
|
|
36
|
+
private renderSingleCanvas;
|
|
37
|
+
private renderWaveform;
|
|
38
|
+
render(audioData: AudioBuffer): void;
|
|
39
|
+
reRender(): void;
|
|
40
|
+
zoom(minPxPerSec: number): void;
|
|
41
|
+
private scrollIntoView;
|
|
42
|
+
renderProgress(progress: number, isPlaying?: boolean): void;
|
|
43
|
+
}
|
|
44
|
+
export default Renderer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";class t{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)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}function s(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,h=t>>1;r<t;){for(a=0;a<r;a++)this.reverseTable[a+r]=this.reverseTable[a]+h;r<<=1,h>>=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,h=this.sinTable,n=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,k=0;k<a;k++)l[k]=t[n[k]]*this.windowValues[n[k]],o[k]=0;for(;x<a;){d=r[x],w=h[x],M=1,b=0;for(var q=0;q<x;q++){for(k=q;k<a;)m=M*l[g=k+x]-b*o[g],y=M*o[g]+b*l[g],l[g]=l[k]-m,o[g]=o[k]-y,l[k]+=m,o[k]+=y,k+=x<<1;M=(v=M)*d-b*w,b=v*w+b*d}x<<=1}k=0;for(var S=a/2;k<S;k++)(i=c*f((e=l[k])*e+(s=o[k])*s))>this.peak&&(this.peakBand=k,this.peak=i),p[k]=i;return p}}class i extends e{static create(t){return new i(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,h=this.frequencyMax;if(e){for(let n=0;n<t.length;n++){const l=this.resample(t[n]),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-h/a),i,s*(h-r)/a,0,s*n,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),super.destroy()}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.setAttribute("part","spec-labels"),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(this.options.labelsBackground,"12px","12px","",this.options.labelsColor,this.options.labelsHzColor||this.options.labelsColor,"center","#specLabels")}getFrequencies(t){const e=this.fftSamples,i=this.wavesurfer.getDecodedData(),a=this.channels;if(this.frequencyMax=this.frequencyMax||i.sampleRate/2,!i)return;this.buffer=i;const r=i.sampleRate,h=[];let n=this.noverlap;if(!n){const t=i.length/this.canvas.width;n=Math.max(0,Math.round(e-t))}const l=new s(e,r,this.windowFunc,this.alpha);for(let t=0;t<a;t++){const s=i.getChannelData(t),a=[];let r=0;for(;r+e<s.length;){const t=s.slice(r,r+e),i=l.calculateSpectrum(t),h=new Uint8Array(e/2);let o;for(o=0;o<e/2;o++)h[o]=Math.max(-255,45*Math.log10(i[o]));a.push(h),r+=e-n}h.push(a)}t(h,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,h,n){t=t||"rgba(68,68,68,0)",e=e||"12px",s=s||"12px",i=i||"Helvetica",a=a||"#fff",r=r||"#fff",h=h||"center";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 n=0;n<this.channels;n++){let u;for(p.fillStyle=t,p.fillRect(0,n*l,55,(1+n)*l),p.fill(),u=0;u<=o;u++){p.textAlign=h,p.textBaseline="middle";const t=c+f*u,o=this.freqType(t),d=this.unitType(t),w=16;let M;M=0==u?(1+n)*l+u-10:(1+n)*l-50*u+2,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,w+24,M),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,w,M)}}}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 h;for(h=0;h<t.length;h++){const s=h*i,n=s+i,l=r*a,o=l+a,c=n<=l||o<=s?0:Math.min(Math.max(n,l),Math.max(o,s))-Math.max(Math.min(n,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[h][f]}const n=new Uint8Array(t[0].length);let l;for(l=0;l<t[0].length;l++)n[l]=e[l];s.push(n)}return s}}module.exports=i;
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
* ]
|
|
18
18
|
* });
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Spectrogram plugin for wavesurfer.
|
|
22
|
+
*/
|
|
23
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
21
24
|
export type SpectrogramPluginOptions = {
|
|
22
25
|
/** Selector of element or element in which to render */
|
|
23
26
|
container: string | HTMLElement;
|
|
@@ -27,6 +30,9 @@ export type SpectrogramPluginOptions = {
|
|
|
27
30
|
height?: number;
|
|
28
31
|
/** Set to true to display frequency labels. */
|
|
29
32
|
labels?: boolean;
|
|
33
|
+
labelsBackground?: string;
|
|
34
|
+
labelsColor?: string;
|
|
35
|
+
labelsHzColor?: string;
|
|
30
36
|
/** Size of the overlapping window. Must be < fftSamples. Auto deduced from canvas size by default. */
|
|
31
37
|
noverlap?: number;
|
|
32
38
|
/** The window function to be used. */
|
|
@@ -43,11 +49,11 @@ export type SpectrogramPluginOptions = {
|
|
|
43
49
|
*/
|
|
44
50
|
colorMap?: number[][];
|
|
45
51
|
};
|
|
46
|
-
export type SpectrogramPluginEvents = {
|
|
52
|
+
export type SpectrogramPluginEvents = BasePluginEvents & {
|
|
47
53
|
ready: [];
|
|
48
54
|
click: [relativeX: number];
|
|
49
55
|
};
|
|
50
|
-
|
|
56
|
+
declare class SpectrogramPlugin extends BasePlugin<SpectrogramPluginEvents, SpectrogramPluginOptions> {
|
|
51
57
|
static create(options?: SpectrogramPluginOptions): SpectrogramPlugin;
|
|
52
58
|
utils: {
|
|
53
59
|
style: (el: HTMLElement, styles: Record<string, string>) => CSSStyleDeclaration & Record<string, string>;
|
|
@@ -67,3 +73,4 @@ export default class SpectrogramPlugin extends BasePlugin<SpectrogramPluginEvent
|
|
|
67
73
|
loadLabels(bgFill: any, fontSizeFreq: any, fontSizeUnit: any, fontType: any, textColorFreq: any, textColorUnit: any, textAlign: any, container: any): void;
|
|
68
74
|
resample(oldMatrix: any): Uint8Array[];
|
|
69
75
|
}
|
|
76
|
+
export default SpectrogramPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class t{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)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}function s(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,h=t>>1;r<t;){for(a=0;a<r;a++)this.reverseTable[a+r]=this.reverseTable[a]+h;r<<=1,h>>=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,h=this.sinTable,n=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,k=0;k<a;k++)l[k]=t[n[k]]*this.windowValues[n[k]],o[k]=0;for(;x<a;){d=r[x],w=h[x],M=1,b=0;for(var q=0;q<x;q++){for(k=q;k<a;)m=M*l[g=k+x]-b*o[g],y=M*o[g]+b*l[g],l[g]=l[k]-m,o[g]=o[k]-y,l[k]+=m,o[k]+=y,k+=x<<1;M=(v=M)*d-b*w,b=v*w+b*d}x<<=1}k=0;for(var S=a/2;k<S;k++)(i=c*f((e=l[k])*e+(s=o[k])*s))>this.peak&&(this.peakBand=k,this.peak=i),p[k]=i;return p}}class i extends e{static create(t){return new i(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,h=this.frequencyMax;if(e){for(let n=0;n<t.length;n++){const l=this.resample(t[n]),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-h/a),i,s*(h-r)/a,0,s*n,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),super.destroy()}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.setAttribute("part","spec-labels"),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(this.options.labelsBackground,"12px","12px","",this.options.labelsColor,this.options.labelsHzColor||this.options.labelsColor,"center","#specLabels")}getFrequencies(t){const e=this.fftSamples,i=this.wavesurfer.getDecodedData(),a=this.channels;if(this.frequencyMax=this.frequencyMax||i.sampleRate/2,!i)return;this.buffer=i;const r=i.sampleRate,h=[];let n=this.noverlap;if(!n){const t=i.length/this.canvas.width;n=Math.max(0,Math.round(e-t))}const l=new s(e,r,this.windowFunc,this.alpha);for(let t=0;t<a;t++){const s=i.getChannelData(t),a=[];let r=0;for(;r+e<s.length;){const t=s.slice(r,r+e),i=l.calculateSpectrum(t),h=new Uint8Array(e/2);let o;for(o=0;o<e/2;o++)h[o]=Math.max(-255,45*Math.log10(i[o]));a.push(h),r+=e-n}h.push(a)}t(h,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,h,n){t=t||"rgba(68,68,68,0)",e=e||"12px",s=s||"12px",i=i||"Helvetica",a=a||"#fff",r=r||"#fff",h=h||"center";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 n=0;n<this.channels;n++){let u;for(p.fillStyle=t,p.fillRect(0,n*l,55,(1+n)*l),p.fill(),u=0;u<=o;u++){p.textAlign=h,p.textBaseline="middle";const t=c+f*u,o=this.freqType(t),d=this.unitType(t),w=16;let M;M=0==u?(1+n)*l+u-10:(1+n)*l-50*u+2,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,w+24,M),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,w,M)}}}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 h;for(h=0;h<t.length;h++){const s=h*i,n=s+i,l=r*a,o=l+a,c=n<=l||o<=s?0:Math.min(Math.max(n,l),Math.max(o,s))-Math.max(Math.min(n,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[h][f]}const n=new Uint8Array(t[0].length);let l;for(l=0;l<t[0].length;l++)n[l]=e[l];s.push(n)}return s}}export{i as default};
|
|
@@ -18,9 +18,160 @@
|
|
|
18
18
|
* });
|
|
19
19
|
*/
|
|
20
20
|
// @ts-nocheck
|
|
21
|
+
/**
|
|
22
|
+
* Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
|
|
23
|
+
*
|
|
24
|
+
* @param {Number} bufferSize Buffer size
|
|
25
|
+
* @param {Number} sampleRate Sample rate
|
|
26
|
+
* @param {Function} windowFunc Window function
|
|
27
|
+
* @param {Number} alpha Alpha channel
|
|
28
|
+
*/
|
|
29
|
+
function FFT(bufferSize, sampleRate, windowFunc, alpha) {
|
|
30
|
+
this.bufferSize = bufferSize;
|
|
31
|
+
this.sampleRate = sampleRate;
|
|
32
|
+
this.bandwidth = (2 / bufferSize) * (sampleRate / 2);
|
|
33
|
+
this.sinTable = new Float32Array(bufferSize);
|
|
34
|
+
this.cosTable = new Float32Array(bufferSize);
|
|
35
|
+
this.windowValues = new Float32Array(bufferSize);
|
|
36
|
+
this.reverseTable = new Uint32Array(bufferSize);
|
|
37
|
+
this.peakBand = 0;
|
|
38
|
+
this.peak = 0;
|
|
39
|
+
var i;
|
|
40
|
+
switch (windowFunc) {
|
|
41
|
+
case 'bartlett':
|
|
42
|
+
for (i = 0; i < bufferSize; i++) {
|
|
43
|
+
this.windowValues[i] = (2 / (bufferSize - 1)) * ((bufferSize - 1) / 2 - Math.abs(i - (bufferSize - 1) / 2));
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case 'bartlettHann':
|
|
47
|
+
for (i = 0; i < bufferSize; i++) {
|
|
48
|
+
this.windowValues[i] =
|
|
49
|
+
0.62 - 0.48 * Math.abs(i / (bufferSize - 1) - 0.5) - 0.38 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case 'blackman':
|
|
53
|
+
alpha = alpha || 0.16;
|
|
54
|
+
for (i = 0; i < bufferSize; i++) {
|
|
55
|
+
this.windowValues[i] =
|
|
56
|
+
(1 - alpha) / 2 -
|
|
57
|
+
0.5 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1)) +
|
|
58
|
+
(alpha / 2) * Math.cos((4 * Math.PI * i) / (bufferSize - 1));
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case 'cosine':
|
|
62
|
+
for (i = 0; i < bufferSize; i++) {
|
|
63
|
+
this.windowValues[i] = Math.cos((Math.PI * i) / (bufferSize - 1) - Math.PI / 2);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case 'gauss':
|
|
67
|
+
alpha = alpha || 0.25;
|
|
68
|
+
for (i = 0; i < bufferSize; i++) {
|
|
69
|
+
this.windowValues[i] = Math.pow(Math.E, -0.5 * Math.pow((i - (bufferSize - 1) / 2) / ((alpha * (bufferSize - 1)) / 2), 2));
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case 'hamming':
|
|
73
|
+
for (i = 0; i < bufferSize; i++) {
|
|
74
|
+
this.windowValues[i] = 0.54 - 0.46 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case 'hann':
|
|
78
|
+
case undefined:
|
|
79
|
+
for (i = 0; i < bufferSize; i++) {
|
|
80
|
+
this.windowValues[i] = 0.5 * (1 - Math.cos((Math.PI * 2 * i) / (bufferSize - 1)));
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 'lanczoz':
|
|
84
|
+
for (i = 0; i < bufferSize; i++) {
|
|
85
|
+
this.windowValues[i] =
|
|
86
|
+
Math.sin(Math.PI * ((2 * i) / (bufferSize - 1) - 1)) / (Math.PI * ((2 * i) / (bufferSize - 1) - 1));
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case 'rectangular':
|
|
90
|
+
for (i = 0; i < bufferSize; i++) {
|
|
91
|
+
this.windowValues[i] = 1;
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case 'triangular':
|
|
95
|
+
for (i = 0; i < bufferSize; i++) {
|
|
96
|
+
this.windowValues[i] = (2 / bufferSize) * (bufferSize / 2 - Math.abs(i - (bufferSize - 1) / 2));
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
throw Error("No such window function '" + windowFunc + "'");
|
|
101
|
+
}
|
|
102
|
+
var limit = 1;
|
|
103
|
+
var bit = bufferSize >> 1;
|
|
104
|
+
var i;
|
|
105
|
+
while (limit < bufferSize) {
|
|
106
|
+
for (i = 0; i < limit; i++) {
|
|
107
|
+
this.reverseTable[i + limit] = this.reverseTable[i] + bit;
|
|
108
|
+
}
|
|
109
|
+
limit = limit << 1;
|
|
110
|
+
bit = bit >> 1;
|
|
111
|
+
}
|
|
112
|
+
for (i = 0; i < bufferSize; i++) {
|
|
113
|
+
this.sinTable[i] = Math.sin(-Math.PI / i);
|
|
114
|
+
this.cosTable[i] = Math.cos(-Math.PI / i);
|
|
115
|
+
}
|
|
116
|
+
this.calculateSpectrum = function (buffer) {
|
|
117
|
+
// Locally scope variables for speed up
|
|
118
|
+
var bufferSize = this.bufferSize, cosTable = this.cosTable, sinTable = this.sinTable, reverseTable = this.reverseTable, real = new Float32Array(bufferSize), imag = new Float32Array(bufferSize), bSi = 2 / this.bufferSize, sqrt = Math.sqrt, rval, ival, mag, spectrum = new Float32Array(bufferSize / 2);
|
|
119
|
+
var k = Math.floor(Math.log(bufferSize) / Math.LN2);
|
|
120
|
+
if (Math.pow(2, k) !== bufferSize) {
|
|
121
|
+
throw 'Invalid buffer size, must be a power of 2.';
|
|
122
|
+
}
|
|
123
|
+
if (bufferSize !== buffer.length) {
|
|
124
|
+
throw ('Supplied buffer is not the same size as defined FFT. FFT Size: ' +
|
|
125
|
+
bufferSize +
|
|
126
|
+
' Buffer Size: ' +
|
|
127
|
+
buffer.length);
|
|
128
|
+
}
|
|
129
|
+
var halfSize = 1, phaseShiftStepReal, phaseShiftStepImag, currentPhaseShiftReal, currentPhaseShiftImag, off, tr, ti, tmpReal;
|
|
130
|
+
for (var i = 0; i < bufferSize; i++) {
|
|
131
|
+
real[i] = buffer[reverseTable[i]] * this.windowValues[reverseTable[i]];
|
|
132
|
+
imag[i] = 0;
|
|
133
|
+
}
|
|
134
|
+
while (halfSize < bufferSize) {
|
|
135
|
+
phaseShiftStepReal = cosTable[halfSize];
|
|
136
|
+
phaseShiftStepImag = sinTable[halfSize];
|
|
137
|
+
currentPhaseShiftReal = 1;
|
|
138
|
+
currentPhaseShiftImag = 0;
|
|
139
|
+
for (var fftStep = 0; fftStep < halfSize; fftStep++) {
|
|
140
|
+
var i = fftStep;
|
|
141
|
+
while (i < bufferSize) {
|
|
142
|
+
off = i + halfSize;
|
|
143
|
+
tr = currentPhaseShiftReal * real[off] - currentPhaseShiftImag * imag[off];
|
|
144
|
+
ti = currentPhaseShiftReal * imag[off] + currentPhaseShiftImag * real[off];
|
|
145
|
+
real[off] = real[i] - tr;
|
|
146
|
+
imag[off] = imag[i] - ti;
|
|
147
|
+
real[i] += tr;
|
|
148
|
+
imag[i] += ti;
|
|
149
|
+
i += halfSize << 1;
|
|
150
|
+
}
|
|
151
|
+
tmpReal = currentPhaseShiftReal;
|
|
152
|
+
currentPhaseShiftReal = tmpReal * phaseShiftStepReal - currentPhaseShiftImag * phaseShiftStepImag;
|
|
153
|
+
currentPhaseShiftImag = tmpReal * phaseShiftStepImag + currentPhaseShiftImag * phaseShiftStepReal;
|
|
154
|
+
}
|
|
155
|
+
halfSize = halfSize << 1;
|
|
156
|
+
}
|
|
157
|
+
for (var i = 0, N = bufferSize / 2; i < N; i++) {
|
|
158
|
+
rval = real[i];
|
|
159
|
+
ival = imag[i];
|
|
160
|
+
mag = bSi * sqrt(rval * rval + ival * ival);
|
|
161
|
+
if (mag > this.peak) {
|
|
162
|
+
this.peakBand = i;
|
|
163
|
+
this.peak = mag;
|
|
164
|
+
}
|
|
165
|
+
spectrum[i] = mag;
|
|
166
|
+
}
|
|
167
|
+
return spectrum;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Spectrogram plugin for wavesurfer.
|
|
172
|
+
*/
|
|
21
173
|
import BasePlugin from '../base-plugin.js';
|
|
22
|
-
|
|
23
|
-
export default class SpectrogramPlugin extends BasePlugin {
|
|
174
|
+
class SpectrogramPlugin extends BasePlugin {
|
|
24
175
|
static create(options) {
|
|
25
176
|
return new SpectrogramPlugin(options || {});
|
|
26
177
|
}
|
|
@@ -127,6 +278,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
127
278
|
this.wrapper.remove();
|
|
128
279
|
this.wrapper = null;
|
|
129
280
|
}
|
|
281
|
+
super.destroy();
|
|
130
282
|
}
|
|
131
283
|
createWrapper() {
|
|
132
284
|
this.wrapper = document.createElement('div');
|
|
@@ -140,6 +292,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
140
292
|
// if labels are active
|
|
141
293
|
if (this.options.labels) {
|
|
142
294
|
const labelsEl = document.createElement('canvas');
|
|
295
|
+
labelsEl.setAttribute('part', 'spec-labels');
|
|
143
296
|
labelsEl.classList.add('spec-labels');
|
|
144
297
|
this.utils.style(labelsEl, {
|
|
145
298
|
position: 'absolute',
|
|
@@ -180,7 +333,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
180
333
|
else {
|
|
181
334
|
this.getFrequencies(this.drawSpectrogram);
|
|
182
335
|
}
|
|
183
|
-
this.loadLabels(
|
|
336
|
+
this.loadLabels(this.options.labelsBackground, '12px', '12px', '', this.options.labelsColor, this.options.labelsHzColor || this.options.labelsColor, 'center', '#specLabels');
|
|
184
337
|
}
|
|
185
338
|
getFrequencies(callback) {
|
|
186
339
|
const fftSamples = this.fftSamples;
|
|
@@ -236,7 +389,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
236
389
|
const frequenciesHeight = this.height;
|
|
237
390
|
bgFill = bgFill || 'rgba(68,68,68,0)';
|
|
238
391
|
fontSizeFreq = fontSizeFreq || '12px';
|
|
239
|
-
fontSizeUnit = fontSizeUnit || '
|
|
392
|
+
fontSizeUnit = fontSizeUnit || '12px';
|
|
240
393
|
fontType = fontType || 'Helvetica';
|
|
241
394
|
textColorFreq = textColorFreq || '#fff';
|
|
242
395
|
textColorUnit = textColorUnit || '#fff';
|
|
@@ -275,26 +428,18 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
275
428
|
let y;
|
|
276
429
|
if (i == 0) {
|
|
277
430
|
y = (1 + c) * getMaxY + i - 10;
|
|
278
|
-
// unit label
|
|
279
|
-
ctx.fillStyle = textColorUnit;
|
|
280
|
-
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
281
|
-
ctx.fillText(units, x + 24, y);
|
|
282
|
-
// freq label
|
|
283
|
-
ctx.fillStyle = textColorFreq;
|
|
284
|
-
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
285
|
-
ctx.fillText(label, x, y);
|
|
286
431
|
}
|
|
287
432
|
else {
|
|
288
433
|
y = (1 + c) * getMaxY - i * 50 + yLabelOffset;
|
|
289
|
-
// unit label
|
|
290
|
-
ctx.fillStyle = textColorUnit;
|
|
291
|
-
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
292
|
-
ctx.fillText(units, x + 24, y);
|
|
293
|
-
// freq label
|
|
294
|
-
ctx.fillStyle = textColorFreq;
|
|
295
|
-
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
296
|
-
ctx.fillText(label, x, y);
|
|
297
434
|
}
|
|
435
|
+
// unit label
|
|
436
|
+
ctx.fillStyle = textColorUnit;
|
|
437
|
+
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
438
|
+
ctx.fillText(units, x + 24, y);
|
|
439
|
+
// freq label
|
|
440
|
+
ctx.fillStyle = textColorFreq;
|
|
441
|
+
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
442
|
+
ctx.fillText(label, x, y);
|
|
298
443
|
}
|
|
299
444
|
}
|
|
300
445
|
}
|
|
@@ -338,3 +483,4 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
338
483
|
return newMatrix;
|
|
339
484
|
}
|
|
340
485
|
}
|
|
486
|
+
export default SpectrogramPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):((t="undefined"!=typeof globalThis?globalThis:t||self).WaveSurfer=t.WaveSurfer||{},t.WaveSurfer.Spectrogram=e())}(this,(function(){"use strict";class t{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)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}function s(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,u=new Float32Array(a/2),p=Math.floor(Math.log(a)/Math.LN2);if(Math.pow(2,p)!==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,b,M,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],b=1,M=0;for(var k=0;k<x;k++){for(S=k;S<a;)m=b*l[g=S+x]-M*o[g],y=b*o[g]+M*l[g],l[g]=l[S]-m,o[g]=o[S]-y,l[S]+=m,o[S]+=y,S+=x<<1;b=(v=b)*d-M*w,M=v*w+M*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),u[S]=i;return u}}class i extends e{static create(t){return new i(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),super.destroy()}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.setAttribute("part","spec-labels"),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(this.options.labelsBackground,"12px","12px","",this.options.labelsColor,this.options.labelsHzColor||this.options.labelsColor,"center","#specLabels")}getFrequencies(t){const e=this.fftSamples,i=this.wavesurfer.getDecodedData(),a=this.channels;if(this.frequencyMax=this.frequencyMax||i.sampleRate/2,!i)return;this.buffer=i;const r=i.sampleRate,n=[];let h=this.noverlap;if(!h){const t=i.length/this.canvas.width;h=Math.max(0,Math.round(e-t))}const l=new s(e,r,this.windowFunc,this.alpha);for(let t=0;t<a;t++){const s=i.getChannelData(t),a=[];let r=0;for(;r+e<s.length;){const t=s.slice(r,r+e),i=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(i[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||"12px",i=i||"Helvetica",a=a||"#fff",r=r||"#fff",n=n||"center";const l=this.height||512,o=l/256*5,c=this.frequencyMin,f=(this.frequencyMax-c)/o,u=this.labelsEl.getContext("2d"),p=window.devicePixelRatio;if(this.labelsEl.height=this.height*this.channels*p,this.labelsEl.width=55*p,u.scale(p,p),u)for(let h=0;h<this.channels;h++){let p;for(u.fillStyle=t,u.fillRect(0,h*l,55,(1+h)*l),u.fill(),p=0;p<=o;p++){u.textAlign=n,u.textBaseline="middle";const t=c+f*p,o=this.freqType(t),d=this.unitType(t),w=16;let b;b=0==p?(1+h)*l+p-10:(1+h)*l-50*p+2,u.fillStyle=r,u.font=s+" "+i,u.fillText(d,w+24,b),u.fillStyle=a,u.font=e+" "+i,u.fillText(o,w,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 i}));
|