wavesurfer.js 7.0.0-beta.1 → 7.0.0-beta.11
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/LICENSE +29 -0
- package/README.md +92 -38
- package/dist/decoder.d.ts +1 -1
- package/dist/draggable.d.ts +1 -0
- package/dist/draggable.js +59 -0
- package/dist/fetcher.d.ts +2 -0
- package/dist/fetcher.js +4 -0
- package/dist/player.d.ts +1 -1
- package/dist/player.js +2 -2
- package/dist/plugins/envelope.js +4 -25
- package/dist/plugins/envelope.min.cjs +1 -0
- package/dist/plugins/minimap.js +0 -3
- package/dist/plugins/minimap.min.cjs +1 -0
- package/dist/plugins/record.d.ts +3 -1
- package/dist/plugins/record.js +11 -5
- package/dist/plugins/record.min.cjs +1 -0
- package/dist/plugins/regions.d.ts +2 -1
- package/dist/plugins/regions.js +40 -81
- package/dist/plugins/regions.min.cjs +1 -0
- package/dist/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/plugins/spectrogram-fft.js +150 -0
- package/dist/plugins/spectrogram.d.ts +3 -0
- package/dist/plugins/spectrogram.js +12 -19
- package/dist/plugins/{spectrogram.min.js → spectrogram.min.cjs} +1 -1
- package/dist/plugins/timeline.min.cjs +1 -0
- package/dist/renderer.d.ts +11 -21
- package/dist/renderer.js +213 -136
- package/dist/wavesurfer.d.ts +17 -14
- package/dist/wavesurfer.js +54 -65
- package/dist/wavesurfer.min.cjs +1 -0
- package/package.json +17 -11
- package/dist/plugins/envelope.min.js +0 -1
- package/dist/plugins/minimap.min.js +0 -1
- package/dist/plugins/multitrack.d.ts +0 -117
- package/dist/plugins/multitrack.js +0 -507
- package/dist/plugins/multitrack.min.js +0 -1
- package/dist/plugins/record.min.js +0 -1
- package/dist/plugins/regions.min.js +0 -1
- package/dist/plugins/timeline.min.js +0 -1
- package/dist/wavesurfer.min.js +0 -1
package/dist/plugins/regions.js
CHANGED
|
@@ -4,47 +4,8 @@
|
|
|
4
4
|
* You can set the color and content of each region, as well as their HTML content.
|
|
5
5
|
*/
|
|
6
6
|
import BasePlugin from '../base-plugin.js';
|
|
7
|
+
import { makeDraggable } from '../draggable.js';
|
|
7
8
|
import EventEmitter from '../event-emitter.js';
|
|
8
|
-
function makeDraggable(element, onStart, onMove, onEnd, threshold = 5) {
|
|
9
|
-
if (!element)
|
|
10
|
-
return () => undefined;
|
|
11
|
-
let isDragging = false;
|
|
12
|
-
const onClick = (e) => {
|
|
13
|
-
isDragging && e.stopPropagation();
|
|
14
|
-
};
|
|
15
|
-
const onMouseDown = (e) => {
|
|
16
|
-
e.stopPropagation();
|
|
17
|
-
let x = e.clientX;
|
|
18
|
-
let sumDx = 0;
|
|
19
|
-
onStart(x);
|
|
20
|
-
const onMouseMove = (e) => {
|
|
21
|
-
const newX = e.clientX;
|
|
22
|
-
const dx = newX - x;
|
|
23
|
-
sumDx += dx;
|
|
24
|
-
x = newX;
|
|
25
|
-
if (isDragging || Math.abs(sumDx) >= threshold) {
|
|
26
|
-
onMove(isDragging ? dx : sumDx);
|
|
27
|
-
isDragging = true;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
const onMouseUp = () => {
|
|
31
|
-
if (isDragging) {
|
|
32
|
-
onEnd();
|
|
33
|
-
setTimeout(() => (isDragging = false), 10);
|
|
34
|
-
}
|
|
35
|
-
document.removeEventListener('mousemove', onMouseMove);
|
|
36
|
-
document.removeEventListener('mouseup', onMouseUp);
|
|
37
|
-
};
|
|
38
|
-
document.addEventListener('mousemove', onMouseMove);
|
|
39
|
-
document.addEventListener('mouseup', onMouseUp);
|
|
40
|
-
};
|
|
41
|
-
element.addEventListener('click', onClick);
|
|
42
|
-
element.addEventListener('mousedown', onMouseDown);
|
|
43
|
-
return () => {
|
|
44
|
-
element.removeEventListener('click', onClick);
|
|
45
|
-
element.removeEventListener('mousedown', onMouseDown);
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
9
|
export class Region extends EventEmitter {
|
|
49
10
|
constructor(params, totalDuration) {
|
|
50
11
|
super();
|
|
@@ -126,16 +87,18 @@ export class Region extends EventEmitter {
|
|
|
126
87
|
}
|
|
127
88
|
initMouseEvents() {
|
|
128
89
|
const { element } = this;
|
|
90
|
+
if (!element)
|
|
91
|
+
return;
|
|
129
92
|
element.addEventListener('click', (e) => this.emit('click', e));
|
|
130
93
|
element.addEventListener('mouseenter', (e) => this.emit('over', e));
|
|
131
94
|
element.addEventListener('mouseleave', (e) => this.emit('leave', e));
|
|
132
95
|
element.addEventListener('dblclick', (e) => this.emit('dblclick', e));
|
|
133
96
|
// Drag
|
|
134
|
-
makeDraggable(element, () => this.
|
|
97
|
+
makeDraggable(element, (dx) => this.onMove(dx), () => this.onStartMoving(), () => this.onEndMoving());
|
|
135
98
|
// Resize
|
|
136
99
|
const resizeThreshold = 1;
|
|
137
|
-
makeDraggable(element.querySelector('[data-resize="left"]'), (
|
|
138
|
-
makeDraggable(element.querySelector('[data-resize="right"]'), (
|
|
100
|
+
makeDraggable(element.querySelector('[data-resize="left"]'), (dx) => this.onResize(dx, 'start'), () => null, () => this.onEndResizing(), resizeThreshold);
|
|
101
|
+
makeDraggable(element.querySelector('[data-resize="right"]'), (dx) => this.onResize(dx, 'end'), () => null, () => this.onEndResizing(), resizeThreshold);
|
|
139
102
|
}
|
|
140
103
|
onStartMoving() {
|
|
141
104
|
if (!this.drag)
|
|
@@ -148,7 +111,7 @@ export class Region extends EventEmitter {
|
|
|
148
111
|
this.element.style.cursor = 'grab';
|
|
149
112
|
this.emit('update-end');
|
|
150
113
|
}
|
|
151
|
-
|
|
114
|
+
_onUpdate(dx, sides) {
|
|
152
115
|
if (!this.element.parentElement)
|
|
153
116
|
return;
|
|
154
117
|
const deltaSeconds = (dx / this.element.parentElement.clientWidth) * this.totalDuration;
|
|
@@ -167,12 +130,12 @@ export class Region extends EventEmitter {
|
|
|
167
130
|
onMove(dx) {
|
|
168
131
|
if (!this.drag)
|
|
169
132
|
return;
|
|
170
|
-
this.
|
|
133
|
+
this._onUpdate(dx, ['start', 'end']);
|
|
171
134
|
}
|
|
172
135
|
onResize(dx, side) {
|
|
173
136
|
if (!this.resize)
|
|
174
137
|
return;
|
|
175
|
-
this.
|
|
138
|
+
this._onUpdate(dx, [side]);
|
|
176
139
|
}
|
|
177
140
|
onEndResizing() {
|
|
178
141
|
if (!this.resize)
|
|
@@ -291,6 +254,9 @@ class RegionsPlugin extends BasePlugin {
|
|
|
291
254
|
region.on('click', (e) => {
|
|
292
255
|
this.emit('region-clicked', region, e);
|
|
293
256
|
}),
|
|
257
|
+
region.on('dblclick', (e) => {
|
|
258
|
+
this.emit('region-double-clicked', region, e);
|
|
259
|
+
}),
|
|
294
260
|
// Remove the region from the list when it's removed
|
|
295
261
|
region.once('remove', () => {
|
|
296
262
|
regionSubscriptions.forEach((unsubscribe) => unsubscribe());
|
|
@@ -307,7 +273,7 @@ class RegionsPlugin extends BasePlugin {
|
|
|
307
273
|
const duration = this.wavesurfer.getDuration();
|
|
308
274
|
const region = new Region(options, duration);
|
|
309
275
|
if (!duration) {
|
|
310
|
-
this.subscriptions.push(this.wavesurfer.once('
|
|
276
|
+
this.subscriptions.push(this.wavesurfer.once('ready', (duration) => {
|
|
311
277
|
region._setTotalDuration(duration);
|
|
312
278
|
this.saveRegion(region);
|
|
313
279
|
}));
|
|
@@ -325,50 +291,43 @@ class RegionsPlugin extends BasePlugin {
|
|
|
325
291
|
const wrapper = this.wavesurfer?.getWrapper()?.querySelector('div');
|
|
326
292
|
if (!wrapper)
|
|
327
293
|
return () => undefined;
|
|
294
|
+
const initialSize = 5;
|
|
328
295
|
let region = null;
|
|
329
296
|
let startX = 0;
|
|
330
|
-
let sumDx = 0;
|
|
331
297
|
return makeDraggable(wrapper,
|
|
332
|
-
// On
|
|
333
|
-
(x) =>
|
|
334
|
-
// On mousemove
|
|
335
|
-
(dx) => {
|
|
336
|
-
if (!this.wavesurfer)
|
|
337
|
-
return;
|
|
338
|
-
if (!region) {
|
|
339
|
-
const duration = this.wavesurfer.getDuration();
|
|
340
|
-
const box = wrapper.getBoundingClientRect();
|
|
341
|
-
let start = ((startX - box.left) / box.width) * duration;
|
|
342
|
-
let end = ((startX + dx - box.left) / box.width) * duration;
|
|
343
|
-
if (start > end)
|
|
344
|
-
[start, end] = [end, start];
|
|
345
|
-
region = new Region({
|
|
346
|
-
...options,
|
|
347
|
-
start,
|
|
348
|
-
end,
|
|
349
|
-
}, duration);
|
|
350
|
-
this.regionsContainer.appendChild(region.element);
|
|
351
|
-
}
|
|
352
|
-
sumDx += dx;
|
|
298
|
+
// On drag move
|
|
299
|
+
(dx, _dy, x) => {
|
|
353
300
|
if (region) {
|
|
354
|
-
|
|
355
|
-
|
|
301
|
+
// Update the end position of the region
|
|
302
|
+
// If we're dragging to the left, we need to update the start instead
|
|
303
|
+
region._onUpdate(dx, [x > startX ? 'end' : 'start']);
|
|
356
304
|
}
|
|
357
305
|
},
|
|
358
|
-
// On
|
|
306
|
+
// On drag start
|
|
307
|
+
(x) => {
|
|
308
|
+
startX = x;
|
|
309
|
+
if (!this.wavesurfer)
|
|
310
|
+
return;
|
|
311
|
+
const duration = this.wavesurfer.getDuration();
|
|
312
|
+
const width = this.wavesurfer.getWrapper().clientWidth;
|
|
313
|
+
// Calculate the start time of the region
|
|
314
|
+
const start = (x / width) * duration;
|
|
315
|
+
// Give the region a small initial size
|
|
316
|
+
const end = ((x + initialSize) / width) * duration;
|
|
317
|
+
// Create a region but don't save it until the drag ends
|
|
318
|
+
region = new Region({
|
|
319
|
+
...options,
|
|
320
|
+
start,
|
|
321
|
+
end,
|
|
322
|
+
}, duration);
|
|
323
|
+
// Just add it to the DOM for now
|
|
324
|
+
this.regionsContainer.appendChild(region.element);
|
|
325
|
+
},
|
|
326
|
+
// On drag end
|
|
359
327
|
() => {
|
|
360
328
|
if (region) {
|
|
361
329
|
this.saveRegion(region);
|
|
362
330
|
region = null;
|
|
363
|
-
sumDx = 0;
|
|
364
|
-
// Prevent a click event on the waveform
|
|
365
|
-
if (this.wavesurfer) {
|
|
366
|
-
const { interact } = this.wavesurfer.options;
|
|
367
|
-
if (interact) {
|
|
368
|
-
this.wavesurfer.toggleInteraction(false);
|
|
369
|
-
setTimeout(() => this.wavesurfer?.toggleInteraction(interact), 10);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
331
|
}
|
|
373
332
|
});
|
|
374
333
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>a});const i=class{constructor(){this.listeners={}}on(e,t){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(t),()=>this.un(e,t)}once(e,t){const i=this.on(e,t),n=this.on(e,(()=>{i(),n()}));return i}un(e,t){this.listeners[e]&&(t?this.listeners[e].delete(t):delete this.listeners[e])}unAll(){this.listeners={}}emit(e,...t){this.listeners[e]&&this.listeners[e].forEach((e=>e(...t)))}},n=class extends i{constructor(e){super(),this.subscriptions=[],this.options=e}onInit(){}init(e){this.wavesurfer=e,this.onInit()}destroy(){this.subscriptions.forEach((e=>e()))}};function s(e,t,i,n,s=5){let r=()=>{};if(!e)return r;const o=o=>{o.preventDefault(),o.stopPropagation();let a=o.clientX,l=o.clientY,d=!1;const h=n=>{n.preventDefault(),n.stopPropagation();const r=n.clientX,o=n.clientY;if(d||Math.abs(r-a)>=s||Math.abs(o-l)>=s){const{left:n,top:s}=e.getBoundingClientRect();d||(d=!0,i?.(a-n,l-s)),t(r-a,o-l,r-n,o-s),a=r,l=o}},c=e=>{d&&(e.preventDefault(),e.stopPropagation())},u=()=>{d&&n?.(),r()};r=()=>{document.removeEventListener("pointermove",h),document.removeEventListener("pointerup",u),document.removeEventListener("pointerleave",u),setTimeout((()=>{document.removeEventListener("click",c,!0)}),10)},document.addEventListener("pointermove",h),document.addEventListener("pointerup",u),document.addEventListener("pointerleave",u),document.addEventListener("click",c,!0)};return e.addEventListener("pointerdown",o),()=>{r(),e.removeEventListener("pointerdown",o)}}class r extends i{constructor(e,t){super(),this.totalDuration=t,this.id=e.id||`region-${Math.random().toString(32).slice(2)}`,this.start=e.start,this.end=e.end??e.start,this.drag=e.drag??!0,this.resize=e.resize??!0,this.color=e.color??"rgba(0, 0, 0, 0.1)",this.element=this.initElement(e.content),this.renderPosition(),this.initMouseEvents()}initElement(e){const t=document.createElement("div"),i=this.start===this.end;if(t.setAttribute("part",`${i?"marker":"region"} ${this.id}`),t.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 padding: 0.2em ${i?.2:.4}em;\n pointer-events: all;\n `),e&&("string"==typeof e?(this.content=document.createElement("div"),this.content.textContent=e):this.content=e,this.content.setAttribute("part","region-content"),t.appendChild(this.content)),!i){const e=document.createElement("div");e.setAttribute("data-resize","left"),e.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 `),e.setAttribute("part","region-handle region-handle-left");const i=e.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"),t.appendChild(e),t.appendChild(i)}return t}renderPosition(){const e=this.start/this.totalDuration,t=this.end/this.totalDuration;this.element.style.left=100*e+"%",this.element.style.width=100*(t-e)+"%"}initMouseEvents(){const{element:e}=this;e&&(e.addEventListener("click",(e=>this.emit("click",e))),e.addEventListener("mouseenter",(e=>this.emit("over",e))),e.addEventListener("mouseleave",(e=>this.emit("leave",e))),e.addEventListener("dblclick",(e=>this.emit("dblclick",e))),s(e,(e=>this.onMove(e)),(()=>this.onStartMoving()),(()=>this.onEndMoving())),s(e.querySelector('[data-resize="left"]'),(e=>this.onResize(e,"start")),(()=>null),(()=>this.onEndResizing()),1),s(e.querySelector('[data-resize="right"]'),(e=>this.onResize(e,"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(e,t){if(!this.element.parentElement)return;const i=e/this.element.parentElement.clientWidth*this.totalDuration;t.forEach((e=>{this[e]+=i,"start"===e?this.start=Math.max(0,Math.min(this.start,this.end)):this.end=Math.max(this.start,Math.min(this.end,this.totalDuration))})),this.renderPosition(),this.emit("update")}onMove(e){this.drag&&this._onUpdate(e,["start","end"])}onResize(e,t){this.resize&&this._onUpdate(e,[t])}onEndResizing(){this.resize&&this.emit("update-end")}_setTotalDuration(e){this.totalDuration=e,this.renderPosition()}play(){this.emit("play")}setOptions(e){e.color&&(this.color=e.color,this.element.style.backgroundColor=this.color),void 0!==e.drag&&(this.drag=e.drag,this.element.style.cursor=this.drag?"grab":"default"),void 0!==e.resize&&(this.resize=e.resize,this.element.querySelectorAll("[data-resize]").forEach((e=>{e.style.cursor=this.resize?"ew-resize":"default"}))),void 0===e.start&&void 0===e.end||(this.start=e.start??this.start,this.end=e.end??this.end,this.renderPosition())}remove(){this.emit("remove"),this.element.remove(),this.element=null}}class o extends n{constructor(e){super(e),this.regions=[],this.regionsContainer=this.initRegionsContainer()}static create(e){return new o(e)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer)}initRegionsContainer(){const e=document.createElement("div");return e.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 "),e}getRegions(){return this.regions}avoidOverlapping(e){if(!e.content)return;const t=e.content,i=t.getBoundingClientRect().left,n=e.element.scrollWidth,s=this.regions.filter((t=>{if(t===e||!t.content)return!1;const s=t.content.getBoundingClientRect().left,r=t.element.scrollWidth;return i<s+r&&s<i+n})).map((e=>e.content?.getBoundingClientRect().height||0)).reduce(((e,t)=>e+t),0);t.style.marginTop=`${s}px`}saveRegion(e){this.regionsContainer.appendChild(e.element),this.avoidOverlapping(e),this.regions.push(e),this.emit("region-created",e);const t=[e.on("update-end",(()=>{this.avoidOverlapping(e),this.emit("region-updated",e)})),e.on("play",(()=>{this.wavesurfer?.play(),this.wavesurfer?.setTime(e.start)})),e.on("click",(t=>{this.emit("region-clicked",e,t)})),e.on("dblclick",(t=>{this.emit("region-double-clicked",e,t)})),e.once("remove",(()=>{t.forEach((e=>e())),this.regions=this.regions.filter((t=>t!==e))}))];this.subscriptions.push(...t)}addRegion(e){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const t=this.wavesurfer.getDuration(),i=new r(e,t);return t?this.saveRegion(i):this.subscriptions.push(this.wavesurfer.once("ready",(e=>{i._setTotalDuration(e),this.saveRegion(i)}))),i}enableDragSelection(e){const t=this.wavesurfer?.getWrapper()?.querySelector("div");if(!t)return()=>{};let i=null,n=0;return s(t,((e,t,s)=>{i&&i._onUpdate(e,[s>n?"end":"start"])}),(t=>{if(n=t,!this.wavesurfer)return;const s=this.wavesurfer.getDuration(),o=this.wavesurfer.getWrapper().clientWidth;i=new r({...e,start:t/o*s,end:(t+5)/o*s},s),this.regionsContainer.appendChild(i.element)}),(()=>{i&&(this.saveRegion(i),i=null)}))}clearRegions(){this.regions.forEach((e=>e.remove()))}destroy(){this.clearRegions(),super.destroy()}}const a=o;return t.default})()));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
|
|
3
|
+
*
|
|
4
|
+
* @param {Number} bufferSize Buffer size
|
|
5
|
+
* @param {Number} sampleRate Sample rate
|
|
6
|
+
* @param {Function} windowFunc Window function
|
|
7
|
+
* @param {Number} alpha Alpha channel
|
|
8
|
+
*/
|
|
9
|
+
export default function FFT(bufferSize: any, sampleRate: any, windowFunc: any, alpha: any): void;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
|
|
4
|
+
*
|
|
5
|
+
* @param {Number} bufferSize Buffer size
|
|
6
|
+
* @param {Number} sampleRate Sample rate
|
|
7
|
+
* @param {Function} windowFunc Window function
|
|
8
|
+
* @param {Number} alpha Alpha channel
|
|
9
|
+
*/
|
|
10
|
+
export default function FFT(bufferSize, sampleRate, windowFunc, alpha) {
|
|
11
|
+
this.bufferSize = bufferSize;
|
|
12
|
+
this.sampleRate = sampleRate;
|
|
13
|
+
this.bandwidth = (2 / bufferSize) * (sampleRate / 2);
|
|
14
|
+
this.sinTable = new Float32Array(bufferSize);
|
|
15
|
+
this.cosTable = new Float32Array(bufferSize);
|
|
16
|
+
this.windowValues = new Float32Array(bufferSize);
|
|
17
|
+
this.reverseTable = new Uint32Array(bufferSize);
|
|
18
|
+
this.peakBand = 0;
|
|
19
|
+
this.peak = 0;
|
|
20
|
+
var i;
|
|
21
|
+
switch (windowFunc) {
|
|
22
|
+
case 'bartlett':
|
|
23
|
+
for (i = 0; i < bufferSize; i++) {
|
|
24
|
+
this.windowValues[i] = (2 / (bufferSize - 1)) * ((bufferSize - 1) / 2 - Math.abs(i - (bufferSize - 1) / 2));
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
case 'bartlettHann':
|
|
28
|
+
for (i = 0; i < bufferSize; i++) {
|
|
29
|
+
this.windowValues[i] =
|
|
30
|
+
0.62 - 0.48 * Math.abs(i / (bufferSize - 1) - 0.5) - 0.38 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
case 'blackman':
|
|
34
|
+
alpha = alpha || 0.16;
|
|
35
|
+
for (i = 0; i < bufferSize; i++) {
|
|
36
|
+
this.windowValues[i] =
|
|
37
|
+
(1 - alpha) / 2 -
|
|
38
|
+
0.5 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1)) +
|
|
39
|
+
(alpha / 2) * Math.cos((4 * Math.PI * i) / (bufferSize - 1));
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case 'cosine':
|
|
43
|
+
for (i = 0; i < bufferSize; i++) {
|
|
44
|
+
this.windowValues[i] = Math.cos((Math.PI * i) / (bufferSize - 1) - Math.PI / 2);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case 'gauss':
|
|
48
|
+
alpha = alpha || 0.25;
|
|
49
|
+
for (i = 0; i < bufferSize; i++) {
|
|
50
|
+
this.windowValues[i] = Math.pow(Math.E, -0.5 * Math.pow((i - (bufferSize - 1) / 2) / ((alpha * (bufferSize - 1)) / 2), 2));
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case 'hamming':
|
|
54
|
+
for (i = 0; i < bufferSize; i++) {
|
|
55
|
+
this.windowValues[i] = 0.54 - 0.46 * Math.cos((Math.PI * 2 * i) / (bufferSize - 1));
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
case 'hann':
|
|
59
|
+
case undefined:
|
|
60
|
+
for (i = 0; i < bufferSize; i++) {
|
|
61
|
+
this.windowValues[i] = 0.5 * (1 - Math.cos((Math.PI * 2 * i) / (bufferSize - 1)));
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
case 'lanczoz':
|
|
65
|
+
for (i = 0; i < bufferSize; i++) {
|
|
66
|
+
this.windowValues[i] =
|
|
67
|
+
Math.sin(Math.PI * ((2 * i) / (bufferSize - 1) - 1)) / (Math.PI * ((2 * i) / (bufferSize - 1) - 1));
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case 'rectangular':
|
|
71
|
+
for (i = 0; i < bufferSize; i++) {
|
|
72
|
+
this.windowValues[i] = 1;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case 'triangular':
|
|
76
|
+
for (i = 0; i < bufferSize; i++) {
|
|
77
|
+
this.windowValues[i] = (2 / bufferSize) * (bufferSize / 2 - Math.abs(i - (bufferSize - 1) / 2));
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
throw Error("No such window function '" + windowFunc + "'");
|
|
82
|
+
}
|
|
83
|
+
var limit = 1;
|
|
84
|
+
var bit = bufferSize >> 1;
|
|
85
|
+
var i;
|
|
86
|
+
while (limit < bufferSize) {
|
|
87
|
+
for (i = 0; i < limit; i++) {
|
|
88
|
+
this.reverseTable[i + limit] = this.reverseTable[i] + bit;
|
|
89
|
+
}
|
|
90
|
+
limit = limit << 1;
|
|
91
|
+
bit = bit >> 1;
|
|
92
|
+
}
|
|
93
|
+
for (i = 0; i < bufferSize; i++) {
|
|
94
|
+
this.sinTable[i] = Math.sin(-Math.PI / i);
|
|
95
|
+
this.cosTable[i] = Math.cos(-Math.PI / i);
|
|
96
|
+
}
|
|
97
|
+
this.calculateSpectrum = function (buffer) {
|
|
98
|
+
// Locally scope variables for speed up
|
|
99
|
+
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);
|
|
100
|
+
var k = Math.floor(Math.log(bufferSize) / Math.LN2);
|
|
101
|
+
if (Math.pow(2, k) !== bufferSize) {
|
|
102
|
+
throw 'Invalid buffer size, must be a power of 2.';
|
|
103
|
+
}
|
|
104
|
+
if (bufferSize !== buffer.length) {
|
|
105
|
+
throw ('Supplied buffer is not the same size as defined FFT. FFT Size: ' +
|
|
106
|
+
bufferSize +
|
|
107
|
+
' Buffer Size: ' +
|
|
108
|
+
buffer.length);
|
|
109
|
+
}
|
|
110
|
+
var halfSize = 1, phaseShiftStepReal, phaseShiftStepImag, currentPhaseShiftReal, currentPhaseShiftImag, off, tr, ti, tmpReal;
|
|
111
|
+
for (var i = 0; i < bufferSize; i++) {
|
|
112
|
+
real[i] = buffer[reverseTable[i]] * this.windowValues[reverseTable[i]];
|
|
113
|
+
imag[i] = 0;
|
|
114
|
+
}
|
|
115
|
+
while (halfSize < bufferSize) {
|
|
116
|
+
phaseShiftStepReal = cosTable[halfSize];
|
|
117
|
+
phaseShiftStepImag = sinTable[halfSize];
|
|
118
|
+
currentPhaseShiftReal = 1;
|
|
119
|
+
currentPhaseShiftImag = 0;
|
|
120
|
+
for (var fftStep = 0; fftStep < halfSize; fftStep++) {
|
|
121
|
+
var i = fftStep;
|
|
122
|
+
while (i < bufferSize) {
|
|
123
|
+
off = i + halfSize;
|
|
124
|
+
tr = currentPhaseShiftReal * real[off] - currentPhaseShiftImag * imag[off];
|
|
125
|
+
ti = currentPhaseShiftReal * imag[off] + currentPhaseShiftImag * real[off];
|
|
126
|
+
real[off] = real[i] - tr;
|
|
127
|
+
imag[off] = imag[i] - ti;
|
|
128
|
+
real[i] += tr;
|
|
129
|
+
imag[i] += ti;
|
|
130
|
+
i += halfSize << 1;
|
|
131
|
+
}
|
|
132
|
+
tmpReal = currentPhaseShiftReal;
|
|
133
|
+
currentPhaseShiftReal = tmpReal * phaseShiftStepReal - currentPhaseShiftImag * phaseShiftStepImag;
|
|
134
|
+
currentPhaseShiftImag = tmpReal * phaseShiftStepImag + currentPhaseShiftImag * phaseShiftStepReal;
|
|
135
|
+
}
|
|
136
|
+
halfSize = halfSize << 1;
|
|
137
|
+
}
|
|
138
|
+
for (var i = 0, N = bufferSize / 2; i < N; i++) {
|
|
139
|
+
rval = real[i];
|
|
140
|
+
ival = imag[i];
|
|
141
|
+
mag = bSi * sqrt(rval * rval + ival * ival);
|
|
142
|
+
if (mag > this.peak) {
|
|
143
|
+
this.peakBand = i;
|
|
144
|
+
this.peak = mag;
|
|
145
|
+
}
|
|
146
|
+
spectrum[i] = mag;
|
|
147
|
+
}
|
|
148
|
+
return spectrum;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -27,6 +27,9 @@ export type SpectrogramPluginOptions = {
|
|
|
27
27
|
height?: number;
|
|
28
28
|
/** Set to true to display frequency labels. */
|
|
29
29
|
labels?: boolean;
|
|
30
|
+
labelsBackground?: string;
|
|
31
|
+
labelsColor?: string;
|
|
32
|
+
labelsHzColor?: string;
|
|
30
33
|
/** Size of the overlapping window. Must be < fftSamples. Auto deduced from canvas size by default. */
|
|
31
34
|
noverlap?: number;
|
|
32
35
|
/** The window function to be used. */
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
// @ts-nocheck
|
|
21
21
|
import BasePlugin from '../base-plugin.js';
|
|
22
|
-
import FFT from './spectrogram-fft.
|
|
22
|
+
import FFT from './spectrogram-fft.js';
|
|
23
23
|
export default class SpectrogramPlugin extends BasePlugin {
|
|
24
24
|
static create(options) {
|
|
25
25
|
return new SpectrogramPlugin(options || {});
|
|
@@ -140,6 +140,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
140
140
|
// if labels are active
|
|
141
141
|
if (this.options.labels) {
|
|
142
142
|
const labelsEl = document.createElement('canvas');
|
|
143
|
+
labelsEl.setAttribute('part', 'spec-labels');
|
|
143
144
|
labelsEl.classList.add('spec-labels');
|
|
144
145
|
this.utils.style(labelsEl, {
|
|
145
146
|
position: 'absolute',
|
|
@@ -180,7 +181,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
180
181
|
else {
|
|
181
182
|
this.getFrequencies(this.drawSpectrogram);
|
|
182
183
|
}
|
|
183
|
-
this.loadLabels(
|
|
184
|
+
this.loadLabels(this.options.labelsBackground, '12px', '12px', '', this.options.labelsColor, this.options.labelsHzColor || this.options.labelsColor, 'center', '#specLabels');
|
|
184
185
|
}
|
|
185
186
|
getFrequencies(callback) {
|
|
186
187
|
const fftSamples = this.fftSamples;
|
|
@@ -236,7 +237,7 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
236
237
|
const frequenciesHeight = this.height;
|
|
237
238
|
bgFill = bgFill || 'rgba(68,68,68,0)';
|
|
238
239
|
fontSizeFreq = fontSizeFreq || '12px';
|
|
239
|
-
fontSizeUnit = fontSizeUnit || '
|
|
240
|
+
fontSizeUnit = fontSizeUnit || '12px';
|
|
240
241
|
fontType = fontType || 'Helvetica';
|
|
241
242
|
textColorFreq = textColorFreq || '#fff';
|
|
242
243
|
textColorUnit = textColorUnit || '#fff';
|
|
@@ -275,26 +276,18 @@ export default class SpectrogramPlugin extends BasePlugin {
|
|
|
275
276
|
let y;
|
|
276
277
|
if (i == 0) {
|
|
277
278
|
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
279
|
}
|
|
287
280
|
else {
|
|
288
281
|
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
282
|
}
|
|
283
|
+
// unit label
|
|
284
|
+
ctx.fillStyle = textColorUnit;
|
|
285
|
+
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
286
|
+
ctx.fillText(units, x + 24, y);
|
|
287
|
+
// freq label
|
|
288
|
+
ctx.fillStyle = textColorFreq;
|
|
289
|
+
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
290
|
+
ctx.fillText(label, x, y);
|
|
298
291
|
}
|
|
299
292
|
}
|
|
300
293
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Spectrogram=e():t.Spectrogram=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,s)=>{for(var i in s)t.o(s,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:s[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>r});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const s=this.on(t,e),i=this.on(t,(()=>{s(),i()}));return s}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},i=class extends s{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}};function a(t,e,s,i){switch(this.bufferSize=t,this.sampleRate=e,this.bandwidth=2/t*(e/2),this.sinTable=new Float32Array(t),this.cosTable=new Float32Array(t),this.windowValues=new Float32Array(t),this.reverseTable=new Uint32Array(t),this.peakBand=0,this.peak=0,s){case"bartlett":for(a=0;a<t;a++)this.windowValues[a]=2/(t-1)*((t-1)/2-Math.abs(a-(t-1)/2));break;case"bartlettHann":for(a=0;a<t;a++)this.windowValues[a]=.62-.48*Math.abs(a/(t-1)-.5)-.38*Math.cos(2*Math.PI*a/(t-1));break;case"blackman":for(i=i||.16,a=0;a<t;a++)this.windowValues[a]=(1-i)/2-.5*Math.cos(2*Math.PI*a/(t-1))+i/2*Math.cos(4*Math.PI*a/(t-1));break;case"cosine":for(a=0;a<t;a++)this.windowValues[a]=Math.cos(Math.PI*a/(t-1)-Math.PI/2);break;case"gauss":for(i=i||.25,a=0;a<t;a++)this.windowValues[a]=Math.pow(Math.E,-.5*Math.pow((a-(t-1)/2)/(i*(t-1)/2),2));break;case"hamming":for(a=0;a<t;a++)this.windowValues[a]=.54-.46*Math.cos(2*Math.PI*a/(t-1));break;case"hann":case void 0:for(a=0;a<t;a++)this.windowValues[a]=.5*(1-Math.cos(2*Math.PI*a/(t-1)));break;case"lanczoz":for(a=0;a<t;a++)this.windowValues[a]=Math.sin(Math.PI*(2*a/(t-1)-1))/(Math.PI*(2*a/(t-1)-1));break;case"rectangular":for(a=0;a<t;a++)this.windowValues[a]=1;break;case"triangular":for(a=0;a<t;a++)this.windowValues[a]=2/t*(t/2-Math.abs(a-(t-1)/2));break;default:throw Error("No such window function '"+s+"'")}for(var a,r=1,n=t>>1;r<t;){for(a=0;a<r;a++)this.reverseTable[a+r]=this.reverseTable[a]+n;r<<=1,n>>=1}for(a=0;a<t;a++)this.sinTable[a]=Math.sin(-Math.PI/a),this.cosTable[a]=Math.cos(-Math.PI/a);this.calculateSpectrum=function(t){var e,s,i,a=this.bufferSize,r=this.cosTable,n=this.sinTable,h=this.reverseTable,
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Spectrogram=e():t.Spectrogram=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,s)=>{for(var i in s)t.o(s,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:s[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>r});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const s=this.on(t,e),i=this.on(t,(()=>{s(),i()}));return s}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},i=class extends s{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}};function a(t,e,s,i){switch(this.bufferSize=t,this.sampleRate=e,this.bandwidth=2/t*(e/2),this.sinTable=new Float32Array(t),this.cosTable=new Float32Array(t),this.windowValues=new Float32Array(t),this.reverseTable=new Uint32Array(t),this.peakBand=0,this.peak=0,s){case"bartlett":for(a=0;a<t;a++)this.windowValues[a]=2/(t-1)*((t-1)/2-Math.abs(a-(t-1)/2));break;case"bartlettHann":for(a=0;a<t;a++)this.windowValues[a]=.62-.48*Math.abs(a/(t-1)-.5)-.38*Math.cos(2*Math.PI*a/(t-1));break;case"blackman":for(i=i||.16,a=0;a<t;a++)this.windowValues[a]=(1-i)/2-.5*Math.cos(2*Math.PI*a/(t-1))+i/2*Math.cos(4*Math.PI*a/(t-1));break;case"cosine":for(a=0;a<t;a++)this.windowValues[a]=Math.cos(Math.PI*a/(t-1)-Math.PI/2);break;case"gauss":for(i=i||.25,a=0;a<t;a++)this.windowValues[a]=Math.pow(Math.E,-.5*Math.pow((a-(t-1)/2)/(i*(t-1)/2),2));break;case"hamming":for(a=0;a<t;a++)this.windowValues[a]=.54-.46*Math.cos(2*Math.PI*a/(t-1));break;case"hann":case void 0:for(a=0;a<t;a++)this.windowValues[a]=.5*(1-Math.cos(2*Math.PI*a/(t-1)));break;case"lanczoz":for(a=0;a<t;a++)this.windowValues[a]=Math.sin(Math.PI*(2*a/(t-1)-1))/(Math.PI*(2*a/(t-1)-1));break;case"rectangular":for(a=0;a<t;a++)this.windowValues[a]=1;break;case"triangular":for(a=0;a<t;a++)this.windowValues[a]=2/t*(t/2-Math.abs(a-(t-1)/2));break;default:throw Error("No such window function '"+s+"'")}for(var a,r=1,n=t>>1;r<t;){for(a=0;a<r;a++)this.reverseTable[a+r]=this.reverseTable[a]+n;r<<=1,n>>=1}for(a=0;a<t;a++)this.sinTable[a]=Math.sin(-Math.PI/a),this.cosTable[a]=Math.cos(-Math.PI/a);this.calculateSpectrum=function(t){var e,s,i,a=this.bufferSize,r=this.cosTable,n=this.sinTable,h=this.reverseTable,o=new Float32Array(a),l=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,b,M,g,m,y,v,x=1,S=0;S<a;S++)o[S]=t[h[S]]*this.windowValues[h[S]],l[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*o[g=S+x]-M*l[g],y=b*l[g]+M*o[g],o[g]=o[S]-m,l[g]=l[S]-y,o[S]+=m,l[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=o[S])*e+(s=l[S])*s))>this.peak&&(this.peakBand=S,this.peak=i),p[S]=i;return p}}class r extends i{static create(t){return new r(t||{})}constructor(t){if(super(t),this.utils={style:(t,e)=>Object.assign(t.style,e)},this.drawSpectrogram=t=>{isNaN(t[0][0])||(t=[t]);const e=this.spectrCc,s=this.height,i=this.width,a=this.buffer.sampleRate/2,r=this.frequencyMin,n=this.frequencyMax;if(e){for(let h=0;h<t.length;h++){const o=this.resample(t[h]),l=new ImageData(i,s);for(let t=0;t<o.length;t++)for(let e=0;e<o[t].length;e++){const a=this.colorMap[o[t][e]],r=4*((s-e)*i+t);l.data[r]=255*a[0],l.data[r+1]=255*a[1],l.data[r+2]=255*a[2],l.data[r+3]=255*a[3]}createImageBitmap(l).then((t=>{e.drawImage(t,0,s*(1-n/a),i,s*(n-r)/a,0,s*h,i,s)}))}this.emit("ready")}},this.frequenciesDataUrl=t.frequenciesDataUrl,this.container="string"==typeof t.container?document.querySelector(t.container):t.container,t.colorMap){if(t.colorMap.length<256)throw new Error("Colormap must contain 256 elements");for(let e=0;e<t.colorMap.length;e++)if(4!==t.colorMap[e].length)throw new Error("ColorMap entries must contain 4 values");this.colorMap=t.colorMap}else{this.colorMap=[];for(let t=0;t<256;t++){const e=(255-t)/256;this.colorMap.push([e,e,e,1])}}this.fftSamples=t.fftSamples||512,this.height=t.height||this.fftSamples/2,this.noverlap=t.noverlap,this.windowFunc=t.windowFunc,this.alpha=t.alpha,this.channels=1,this.frequencyMin=t.frequencyMin||0,this.frequencyMax=t.frequencyMax||0,this.createWrapper(),this.createCanvas()}onInit(){this.container=this.container||this.wavesurfer.getWrapper(),this.container.appendChild(this.wrapper),this.wavesurfer.options.fillParent&&this.utils.style(this.wrapper,{width:"100%",overflowX:"hidden",overflowY:"hidden"}),this.width=this.wavesurfer.getWrapper().offsetWidth,this.subscriptions.push(this.wavesurfer.on("redraw",(()=>this.render())))}destroy(){this.unAll(),this.wavesurfer.un("ready",this._onReady),this.wavesurfer.un("redraw",this._onRender),this.wavesurfer=null,this.util=null,this.options=null,this.wrapper&&(this.wrapper.remove(),this.wrapper=null)}createWrapper(){if(this.wrapper=document.createElement("div"),this.utils.style(this.wrapper,{display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none",height:this.height*this.channels+"px"}),this.options.labels){const t=document.createElement("canvas");t.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,s=this.wavesurfer.getDecodedData(),i=this.channels;if(this.frequencyMax=this.frequencyMax||s.sampleRate/2,!s)return;this.buffer=s;const r=s.sampleRate,n=[];let h=this.noverlap;if(!h){const t=s.length/this.canvas.width;h=Math.max(0,Math.round(e-t))}const o=new a(e,r,this.windowFunc,this.alpha);for(let t=0;t<i;t++){const i=s.getChannelData(t),a=[];let r=0;for(;r+e<i.length;){const t=i.slice(r,r+e),s=o.calculateSpectrum(t),n=new Uint8Array(e/2);let l;for(l=0;l<e/2;l++)n[l]=Math.max(-255,45*Math.log10(s[l]));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",h=h||"#specLabels";const o=this.height||512,l=o/256*5,c=this.frequencyMin,f=(this.frequencyMax-c)/l,p=this.labelsEl.getContext("2d"),u=window.devicePixelRatio;if(this.labelsEl.height=this.height*this.channels*u,this.labelsEl.width=55*u,p.scale(u,u),p)for(let h=0;h<this.channels;h++){let u;for(p.fillStyle=t,p.fillRect(0,h*o,55,(1+h)*o),p.fill(),u=0;u<=l;u++){p.textAlign=n,p.textBaseline="middle";const t=c+f*u,l=this.freqType(t),d=this.unitType(t),w=16;let b;b=0==u?(1+h)*o+u-10:(1+h)*o-50*u+2,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,w+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(l,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,o=r*a,l=o+a,c=h<=o||l<=s?0:Math.min(Math.max(h,o),Math.max(l,s))-Math.max(Math.min(h,o),Math.min(l,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 o;for(o=0;o<t[0].length;o++)h[o]=e[o];s.push(h)}return s}}return e.default})()));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Timeline=e():t.Timeline=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>o});const i=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const 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)))}},n=class extends i{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}},s={height:20};class r extends n{constructor(t){super(t||{}),this.options=Object.assign({},s,t),this.timelineWrapper=this.initTimelineWrapper()}static create(t){return new r(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const t=this.options.container??this.wavesurfer.getWrapper();this.options.insertPosition?(t.firstElementChild||t).insertAdjacentElement(this.options.insertPosition,this.timelineWrapper):t.appendChild(this.timelineWrapper),this.options.duration?this.initTimeline(this.options.duration):this.subscriptions.push(this.wavesurfer.on("redraw",(()=>{this.initTimeline(this.wavesurfer?.getDuration()||0)})))}destroy(){this.timelineWrapper.remove(),super.destroy()}initTimelineWrapper(){const t=document.createElement("div");return t.setAttribute("part","timeline"),t}formatTime(t){return t/60>1?`${Math.floor(t/60)}:${(t=Math.round(t%60))<10?"0":""}${t}`:""+Math.round(1e3*t)/1e3}defaultTimeInterval(t){return t>=25?1:5*t>=25?5:15*t>=25?15:60*Math.ceil(.5/t)}defaultPrimaryLabelInterval(t){return t>=25?10:5*t>=25?6:4}defaultSecondaryLabelInterval(t){return t>=25?5:2}initTimeline(t){const e=this.timelineWrapper.scrollWidth/t,i=this.options.timeInterval??this.defaultTimeInterval(e),n=this.options.primaryLabelInterval??this.defaultPrimaryLabelInterval(e),s=this.options.secondaryLabelInterval??this.defaultSecondaryLabelInterval(e),r="beforebegin"===this.options.insertPosition,o=document.createElement("div");if(o.setAttribute("style",`\n height: ${this.options.height}px;\n overflow: hidden;\n display: flex;\n justify-content: space-between;\n align-items: ${r?"flex-start":"flex-end"};\n font-size: ${this.options.height/2}px;\n white-space: nowrap;\n `),r){const t="\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n z-index: 2;\n ";o.setAttribute("style",o.getAttribute("style")+t)}"string"==typeof this.options.style?o.setAttribute("style",o.getAttribute("style")+this.options.style):"object"==typeof this.options.style&&Object.assign(o.style,this.options.style);const l=document.createElement("div");l.setAttribute("style",`\n width: 1px;\n height: 50%;\n display: flex;\n flex-direction: column;\n justify-content: ${r?"flex-start":"flex-end"};\n overflow: visible;\n border-left: 1px solid currentColor;\n opacity: 0.25;\n `);for(let e=0;e<t;e+=i){const t=l.cloneNode(),i=Math.round(100*e)/100%n==0,r=Math.round(100*e)/100%s==0;(i||r)&&(t.style.height="100%",t.style.textIndent="3px",t.textContent=this.formatTime(e),i&&(t.style.opacity="1")),o.appendChild(t)}this.timelineWrapper.innerHTML="",this.timelineWrapper.appendChild(o),this.emit("ready")}}const o=r;return e.default})()));
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
|
-
|
|
3
|
-
height: number;
|
|
4
|
-
waveColor: string;
|
|
5
|
-
progressColor: string;
|
|
6
|
-
cursorColor?: string;
|
|
7
|
-
cursorWidth: number;
|
|
8
|
-
minPxPerSec: number;
|
|
9
|
-
fillParent: boolean;
|
|
10
|
-
barWidth?: number;
|
|
11
|
-
barGap?: number;
|
|
12
|
-
barRadius?: number;
|
|
13
|
-
barHeight?: number;
|
|
14
|
-
hideScrollbar?: boolean;
|
|
15
|
-
autoCenter?: boolean;
|
|
16
|
-
autoScroll?: boolean;
|
|
17
|
-
};
|
|
2
|
+
import type { WaveSurferOptions } from './wavesurfer.js';
|
|
18
3
|
type RendererEvents = {
|
|
19
4
|
click: [relativeX: number];
|
|
20
5
|
drag: [relativeX: number];
|
|
@@ -24,27 +9,32 @@ type RendererEvents = {
|
|
|
24
9
|
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
25
10
|
private static MAX_CANVAS_WIDTH;
|
|
26
11
|
private options;
|
|
12
|
+
private parent;
|
|
27
13
|
private container;
|
|
28
14
|
private scrollContainer;
|
|
29
15
|
private wrapper;
|
|
30
16
|
private canvasWrapper;
|
|
31
17
|
private progressWrapper;
|
|
32
18
|
private cursor;
|
|
33
|
-
private
|
|
19
|
+
private timeouts;
|
|
34
20
|
private isScrolling;
|
|
35
21
|
private audioData;
|
|
36
22
|
private resizeObserver;
|
|
37
23
|
private isDragging;
|
|
38
|
-
constructor(
|
|
24
|
+
constructor(options: WaveSurferOptions);
|
|
39
25
|
private initEvents;
|
|
40
26
|
private initDrag;
|
|
27
|
+
private getHeight;
|
|
41
28
|
private initHtml;
|
|
42
|
-
setOptions(options:
|
|
29
|
+
setOptions(options: WaveSurferOptions): void;
|
|
43
30
|
getWrapper(): HTMLElement;
|
|
44
31
|
getScroll(): number;
|
|
45
32
|
destroy(): void;
|
|
46
|
-
private
|
|
47
|
-
private
|
|
33
|
+
private createDelay;
|
|
34
|
+
private convertColorValues;
|
|
35
|
+
private renderBars;
|
|
36
|
+
private renderSingleCanvas;
|
|
37
|
+
private renderWaveform;
|
|
48
38
|
render(audioData: AudioBuffer): void;
|
|
49
39
|
reRender(): void;
|
|
50
40
|
zoom(minPxPerSec: number): void;
|