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.
Files changed (40) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +92 -38
  3. package/dist/decoder.d.ts +1 -1
  4. package/dist/draggable.d.ts +1 -0
  5. package/dist/draggable.js +59 -0
  6. package/dist/fetcher.d.ts +2 -0
  7. package/dist/fetcher.js +4 -0
  8. package/dist/player.d.ts +1 -1
  9. package/dist/player.js +2 -2
  10. package/dist/plugins/envelope.js +4 -25
  11. package/dist/plugins/envelope.min.cjs +1 -0
  12. package/dist/plugins/minimap.js +0 -3
  13. package/dist/plugins/minimap.min.cjs +1 -0
  14. package/dist/plugins/record.d.ts +3 -1
  15. package/dist/plugins/record.js +11 -5
  16. package/dist/plugins/record.min.cjs +1 -0
  17. package/dist/plugins/regions.d.ts +2 -1
  18. package/dist/plugins/regions.js +40 -81
  19. package/dist/plugins/regions.min.cjs +1 -0
  20. package/dist/plugins/spectrogram-fft.d.ts +9 -0
  21. package/dist/plugins/spectrogram-fft.js +150 -0
  22. package/dist/plugins/spectrogram.d.ts +3 -0
  23. package/dist/plugins/spectrogram.js +12 -19
  24. package/dist/plugins/{spectrogram.min.js → spectrogram.min.cjs} +1 -1
  25. package/dist/plugins/timeline.min.cjs +1 -0
  26. package/dist/renderer.d.ts +11 -21
  27. package/dist/renderer.js +213 -136
  28. package/dist/wavesurfer.d.ts +17 -14
  29. package/dist/wavesurfer.js +54 -65
  30. package/dist/wavesurfer.min.cjs +1 -0
  31. package/package.json +17 -11
  32. package/dist/plugins/envelope.min.js +0 -1
  33. package/dist/plugins/minimap.min.js +0 -1
  34. package/dist/plugins/multitrack.d.ts +0 -117
  35. package/dist/plugins/multitrack.js +0 -507
  36. package/dist/plugins/multitrack.min.js +0 -1
  37. package/dist/plugins/record.min.js +0 -1
  38. package/dist/plugins/regions.min.js +0 -1
  39. package/dist/plugins/timeline.min.js +0 -1
  40. package/dist/wavesurfer.min.js +0 -1
@@ -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.onStartMoving(), (dx) => this.onMove(dx), () => this.onEndMoving());
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"]'), () => null, (dx) => this.onResize(dx, 'start'), () => this.onEndResizing(), resizeThreshold);
138
- makeDraggable(element.querySelector('[data-resize="right"]'), () => null, (dx) => this.onResize(dx, 'end'), () => this.onEndResizing(), resizeThreshold);
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
- onUpdate(dx, sides) {
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.onUpdate(dx, ['start', 'end']);
133
+ this._onUpdate(dx, ['start', 'end']);
171
134
  }
172
135
  onResize(dx, side) {
173
136
  if (!this.resize)
174
137
  return;
175
- this.onUpdate(dx, [side]);
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('canplay', (duration) => {
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 mousedown
333
- (x) => (startX = 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
- const privateRegion = region;
355
- privateRegion.onUpdate(dx, [sumDx > 0 ? 'end' : 'start']);
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 mouseup
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.mjs';
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('rgba(68,68,68,0.5)', '12px', '10px', '', '#fff', '#f7f7f7', 'center', '#specLabels');
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 || '10px';
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,l=new Float32Array(a),o=new Float32Array(a),c=2/this.bufferSize,f=Math.sqrt,p=new Float32Array(a/2),u=Math.floor(Math.log(a)/Math.LN2);if(Math.pow(2,u)!==a)throw"Invalid buffer size, must be a power of 2.";if(a!==t.length)throw"Supplied buffer is not the same size as defined FFT. FFT Size: "+a+" Buffer Size: "+t.length;for(var d,w,M,b,g,m,y,v,x=1,S=0;S<a;S++)l[S]=t[h[S]]*this.windowValues[h[S]],o[S]=0;for(;x<a;){d=r[x],w=n[x],M=1,b=0;for(var k=0;k<x;k++){for(S=k;S<a;)m=M*l[g=S+x]-b*o[g],y=M*o[g]+b*l[g],l[g]=l[S]-m,o[g]=o[S]-y,l[S]+=m,o[S]+=y,S+=x<<1;M=(v=M)*d-b*w,b=v*w+b*d}x<<=1}S=0;for(var q=a/2;S<q;S++)(i=c*f((e=l[S])*e+(s=o[S])*s))>this.peak&&(this.peakBand=S,this.peak=i),p[S]=i;return p}}class r extends i{static create(t){return new r(t||{})}constructor(t){if(super(t),this.utils={style:(t,e)=>Object.assign(t.style,e)},this.drawSpectrogram=t=>{isNaN(t[0][0])||(t=[t]);const e=this.spectrCc,s=this.height,i=this.width,a=this.buffer.sampleRate/2,r=this.frequencyMin,n=this.frequencyMax;if(e){for(let h=0;h<t.length;h++){const l=this.resample(t[h]),o=new ImageData(i,s);for(let t=0;t<l.length;t++)for(let e=0;e<l[t].length;e++){const a=this.colorMap[l[t][e]],r=4*((s-e)*i+t);o.data[r]=255*a[0],o.data[r+1]=255*a[1],o.data[r+2]=255*a[2],o.data[r+3]=255*a[3]}createImageBitmap(o).then((t=>{e.drawImage(t,0,s*(1-n/a),i,s*(n-r)/a,0,s*h,i,s)}))}this.emit("ready")}},this.frequenciesDataUrl=t.frequenciesDataUrl,this.container="string"==typeof t.container?document.querySelector(t.container):t.container,t.colorMap){if(t.colorMap.length<256)throw new Error("Colormap must contain 256 elements");for(let e=0;e<t.colorMap.length;e++)if(4!==t.colorMap[e].length)throw new Error("ColorMap entries must contain 4 values");this.colorMap=t.colorMap}else{this.colorMap=[];for(let t=0;t<256;t++){const e=(255-t)/256;this.colorMap.push([e,e,e,1])}}this.fftSamples=t.fftSamples||512,this.height=t.height||this.fftSamples/2,this.noverlap=t.noverlap,this.windowFunc=t.windowFunc,this.alpha=t.alpha,this.channels=1,this.frequencyMin=t.frequencyMin||0,this.frequencyMax=t.frequencyMax||0,this.createWrapper(),this.createCanvas()}onInit(){this.container=this.container||this.wavesurfer.getWrapper(),this.container.appendChild(this.wrapper),this.wavesurfer.options.fillParent&&this.utils.style(this.wrapper,{width:"100%",overflowX:"hidden",overflowY:"hidden"}),this.width=this.wavesurfer.getWrapper().offsetWidth,this.subscriptions.push(this.wavesurfer.on("redraw",(()=>this.render())))}destroy(){this.unAll(),this.wavesurfer.un("ready",this._onReady),this.wavesurfer.un("redraw",this._onRender),this.wavesurfer=null,this.util=null,this.options=null,this.wrapper&&(this.wrapper.remove(),this.wrapper=null)}createWrapper(){if(this.wrapper=document.createElement("div"),this.utils.style(this.wrapper,{display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none",height:this.height*this.channels+"px"}),this.options.labels){const t=document.createElement("canvas");t.classList.add("spec-labels"),this.utils.style(t,{position:"absolute",zIndex:9,width:"55px",height:"100%"}),this.wrapper.appendChild(t),this.labelsEl=t}this.wrapper.addEventListener("click",this._onWrapperClick)}_wrapperClickHandler(t){t.preventDefault();const e="offsetX"in t?t.offsetX:t.layerX;this.emit("click",e/this.width||0)}createCanvas(){const t=document.createElement("canvas");this.wrapper.appendChild(t),this.spectrCc=t.getContext("2d"),this.utils.style(t,{position:"absolute",left:0,top:0,width:"100%",height:"100%",zIndex:4}),this.canvas=t}render(){this.canvas.width=this.width,this.canvas.height=this.height,this.frequenciesDataUrl?this.loadFrequenciesData(this.frequenciesDataUrl):this.getFrequencies(this.drawSpectrogram),this.loadLabels("rgba(68,68,68,0.5)","12px","10px","","#fff","#f7f7f7","center","#specLabels")}getFrequencies(t){const e=this.fftSamples,s=this.wavesurfer.getDecodedData(),i=this.channels;if(this.frequencyMax=this.frequencyMax||s.sampleRate/2,!s)return;this.buffer=s;const r=s.sampleRate,n=[];let h=this.noverlap;if(!h){const t=s.length/this.canvas.width;h=Math.max(0,Math.round(e-t))}const l=new a(e,r,this.windowFunc,this.alpha);for(let t=0;t<i;t++){const i=s.getChannelData(t),a=[];let r=0;for(;r+e<i.length;){const t=i.slice(r,r+e),s=l.calculateSpectrum(t),n=new Uint8Array(e/2);let o;for(o=0;o<e/2;o++)n[o]=Math.max(-255,45*Math.log10(s[o]));a.push(n),r+=e-h}n.push(a)}t(n,this)}loadFrequenciesData(t){return fetch(t).then((t=>t.json())).then(this.drawSpectrogram)}freqType(t){return t>=1e3?(t/1e3).toFixed(1):Math.round(t)}unitType(t){return t>=1e3?"KHz":"Hz"}loadLabels(t,e,s,i,a,r,n,h){t=t||"rgba(68,68,68,0)",e=e||"12px",s=s||"10px",i=i||"Helvetica",a=a||"#fff",r=r||"#fff",n=n||"center",h=h||"#specLabels";const l=this.height||512,o=l/256*5,c=this.frequencyMin,f=(this.frequencyMax-c)/o,p=this.labelsEl.getContext("2d"),u=window.devicePixelRatio;if(this.labelsEl.height=this.height*this.channels*u,this.labelsEl.width=55*u,p.scale(u,u),p)for(let h=0;h<this.channels;h++){let u;for(p.fillStyle=t,p.fillRect(0,h*l,55,(1+h)*l),p.fill(),u=0;u<=o;u++){p.textAlign=n,p.textBaseline="middle";const t=c+f*u,o=this.freqType(t),d=this.unitType(t),w=2,M=16;let b;0==u?(b=(1+h)*l+u-10,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,M+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,M,b)):(b=(1+h)*l-50*u+w,p.fillStyle=r,p.font=s+" "+i,p.fillText(d,M+24,b),p.fillStyle=a,p.font=e+" "+i,p.fillText(o,M,b))}}}resample(t){const e=this.width,s=[],i=1/t.length,a=1/e;let r;for(r=0;r<e;r++){const e=new Array(t[0].length);let n;for(n=0;n<t.length;n++){const s=n*i,h=s+i,l=r*a,o=l+a,c=h<=l||o<=s?0:Math.min(Math.max(h,l),Math.max(o,s))-Math.max(Math.min(h,l),Math.min(o,s));let f;if(c>0)for(f=0;f<t[0].length;f++)null==e[f]&&(e[f]=0),e[f]+=c/a*t[n][f]}const h=new Uint8Array(t[0].length);let l;for(l=0;l<t[0].length;l++)h[l]=e[l];s.push(h)}return s}}return e.default})()));
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})()));
@@ -1,20 +1,5 @@
1
1
  import EventEmitter from './event-emitter.js';
2
- export type RendererStyleOptions = {
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 timeout;
19
+ private timeouts;
34
20
  private isScrolling;
35
21
  private audioData;
36
22
  private resizeObserver;
37
23
  private isDragging;
38
- constructor(container: HTMLElement | string | null, options: RendererStyleOptions);
24
+ constructor(options: WaveSurferOptions);
39
25
  private initEvents;
40
26
  private initDrag;
27
+ private getHeight;
41
28
  private initHtml;
42
- setOptions(options: RendererStyleOptions): void;
29
+ setOptions(options: WaveSurferOptions): void;
43
30
  getWrapper(): HTMLElement;
44
31
  getScroll(): number;
45
32
  destroy(): void;
46
- private delay;
47
- private renderPeaks;
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;