wavesurfer.js 5.1.0 → 6.0.2
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/CHANGES.md +47 -0
- package/README.md +10 -0
- package/dist/plugin/wavesurfer.cursor.js +53 -51
- package/dist/plugin/wavesurfer.cursor.js.map +1 -1
- package/dist/plugin/wavesurfer.cursor.min.js +2 -2
- package/dist/plugin/wavesurfer.cursor.min.js.map +1 -1
- package/dist/plugin/wavesurfer.elan.js +10 -7
- package/dist/plugin/wavesurfer.elan.js.map +1 -1
- package/dist/plugin/wavesurfer.elan.min.js +2 -2
- package/dist/plugin/wavesurfer.elan.min.js.map +1 -1
- package/dist/plugin/wavesurfer.markers.js +142 -26
- package/dist/plugin/wavesurfer.markers.js.map +1 -1
- package/dist/plugin/wavesurfer.markers.min.js +2 -2
- package/dist/plugin/wavesurfer.markers.min.js.map +1 -1
- package/dist/plugin/wavesurfer.mediasession.js +6 -6
- package/dist/plugin/wavesurfer.mediasession.js.map +1 -1
- package/dist/plugin/wavesurfer.mediasession.min.js +2 -2
- package/dist/plugin/wavesurfer.mediasession.min.js.map +1 -1
- package/dist/plugin/wavesurfer.microphone.js +5 -5
- package/dist/plugin/wavesurfer.microphone.js.map +1 -1
- package/dist/plugin/wavesurfer.microphone.min.js +2 -2
- package/dist/plugin/wavesurfer.microphone.min.js.map +1 -1
- package/dist/plugin/wavesurfer.minimap.js +26 -23
- package/dist/plugin/wavesurfer.minimap.js.map +1 -1
- package/dist/plugin/wavesurfer.minimap.min.js +2 -2
- package/dist/plugin/wavesurfer.minimap.min.js.map +1 -1
- package/dist/plugin/wavesurfer.playhead.js +5 -5
- package/dist/plugin/wavesurfer.playhead.js.map +1 -1
- package/dist/plugin/wavesurfer.playhead.min.js +2 -2
- package/dist/plugin/wavesurfer.playhead.min.js.map +1 -1
- package/dist/plugin/wavesurfer.regions.js +88 -21
- package/dist/plugin/wavesurfer.regions.js.map +1 -1
- package/dist/plugin/wavesurfer.regions.min.js +2 -2
- package/dist/plugin/wavesurfer.regions.min.js.map +1 -1
- package/dist/plugin/wavesurfer.spectrogram.js +71 -58
- package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
- package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
- package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
- package/dist/plugin/wavesurfer.timeline.js +52 -54
- package/dist/plugin/wavesurfer.timeline.js.map +1 -1
- package/dist/plugin/wavesurfer.timeline.min.js +2 -2
- package/dist/plugin/wavesurfer.timeline.min.js.map +1 -1
- package/dist/wavesurfer-html-init.js +6 -6
- package/dist/wavesurfer-html-init.js.map +1 -1
- package/dist/wavesurfer-html-init.min.js +2 -2
- package/dist/wavesurfer-html-init.min.js.map +1 -1
- package/dist/wavesurfer.js +302 -133
- package/dist/wavesurfer.js.map +1 -1
- package/dist/wavesurfer.min.js +2 -2
- package/dist/wavesurfer.min.js.map +1 -1
- package/package.json +23 -23
- package/src/drawer.canvasentry.js +33 -6
- package/src/drawer.js +1 -0
- package/src/drawer.multicanvas.js +19 -8
- package/src/mediaelement.js +1 -1
- package/src/plugin/cursor/index.js +68 -70
- package/src/plugin/markers/index.js +112 -23
- package/src/plugin/minimap/index.js +24 -21
- package/src/plugin/regions/index.js +31 -4
- package/src/plugin/regions/region.js +45 -8
- package/src/plugin/spectrogram/index.js +65 -50
- package/src/plugin/timeline/index.js +11 -9
- package/src/util/index.js +1 -0
- package/src/util/silence-mode.js +35 -0
- package/src/wavesurfer.js +54 -23
- package/src/webaudio.js +10 -1
|
@@ -96,16 +96,7 @@ export default class MinimapPlugin {
|
|
|
96
96
|
this.drawer = new ws.Drawer(this.params.container, this.params);
|
|
97
97
|
this.wavesurfer = ws;
|
|
98
98
|
this.util = ws.util;
|
|
99
|
-
|
|
100
|
-
* Minimap needs to listen for the `ready` and `waveform-ready` events
|
|
101
|
-
* to work with the `MediaElement` backend. The moment the `ready` event
|
|
102
|
-
* is called is different (and peaks would not load).
|
|
103
|
-
*
|
|
104
|
-
* @type {string}
|
|
105
|
-
* @see https://github.com/katspaugh/wavesurfer.js/issues/736
|
|
106
|
-
*/
|
|
107
|
-
this.renderEvent =
|
|
108
|
-
ws.params.backend === 'MediaElement' ? 'waveform-ready' : 'ready';
|
|
99
|
+
this.renderEvent = 'redraw';
|
|
109
100
|
this.overviewRegion = null;
|
|
110
101
|
this.regionsPlugin = this.wavesurfer[this.params.regionsPluginName];
|
|
111
102
|
|
|
@@ -144,7 +135,8 @@ export default class MinimapPlugin {
|
|
|
144
135
|
// event listeners for the overview region
|
|
145
136
|
this._onScroll = e => {
|
|
146
137
|
if (!this.draggingOverview) {
|
|
147
|
-
this.
|
|
138
|
+
const orientedTarget = this.util.withOrientation(e.target, this.wavesurfer.params.vertical);
|
|
139
|
+
this.moveOverviewRegion(orientedTarget.scrollLeft / this.ratio);
|
|
148
140
|
}
|
|
149
141
|
};
|
|
150
142
|
this._onMouseover = e => {
|
|
@@ -245,8 +237,13 @@ export default class MinimapPlugin {
|
|
|
245
237
|
createElements() {
|
|
246
238
|
this.drawer.createElements();
|
|
247
239
|
if (this.params.showOverview) {
|
|
248
|
-
this.overviewRegion = this.util.
|
|
249
|
-
document.createElement('overview'),
|
|
240
|
+
this.overviewRegion = this.util.withOrientation(
|
|
241
|
+
this.drawer.wrapper.appendChild(document.createElement('overview')),
|
|
242
|
+
this.wavesurfer.params.vertical
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
this.util.style(
|
|
246
|
+
this.overviewRegion,
|
|
250
247
|
{
|
|
251
248
|
top: 0,
|
|
252
249
|
bottom: 0,
|
|
@@ -262,7 +259,6 @@ export default class MinimapPlugin {
|
|
|
262
259
|
opacity: this.params.overviewOpacity
|
|
263
260
|
}
|
|
264
261
|
);
|
|
265
|
-
this.drawer.wrapper.appendChild(this.overviewRegion);
|
|
266
262
|
}
|
|
267
263
|
}
|
|
268
264
|
|
|
@@ -307,24 +303,27 @@ export default class MinimapPlugin {
|
|
|
307
303
|
}
|
|
308
304
|
|
|
309
305
|
if (this.params.showOverview) {
|
|
310
|
-
this.overviewRegion.addEventListener('mousedown',
|
|
306
|
+
this.overviewRegion.domElement.addEventListener('mousedown', e => {
|
|
307
|
+
const event = this.util.withOrientation(e, this.wavesurfer.params.vertical);
|
|
311
308
|
this.draggingOverview = true;
|
|
312
309
|
relativePositionX = event.layerX;
|
|
313
310
|
positionMouseDown.clientX = event.clientX;
|
|
314
311
|
positionMouseDown.clientY = event.clientY;
|
|
315
312
|
});
|
|
316
313
|
|
|
317
|
-
this.drawer.wrapper.addEventListener('mousemove',
|
|
314
|
+
this.drawer.wrapper.addEventListener('mousemove', e => {
|
|
318
315
|
if (this.draggingOverview) {
|
|
316
|
+
const event = this.util.withOrientation(e, this.wavesurfer.params.vertical);
|
|
319
317
|
this.moveOverviewRegion(
|
|
320
318
|
event.clientX -
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
this.drawer.container.getBoundingClientRect().left -
|
|
320
|
+
relativePositionX
|
|
323
321
|
);
|
|
324
322
|
}
|
|
325
323
|
});
|
|
326
324
|
|
|
327
|
-
this.drawer.wrapper.addEventListener('mouseup',
|
|
325
|
+
this.drawer.wrapper.addEventListener('mouseup', e => {
|
|
326
|
+
const event = this.util.withOrientation(e, this.wavesurfer.params.vertical);
|
|
328
327
|
if (
|
|
329
328
|
positionMouseDown.clientX - event.clientX === 0 &&
|
|
330
329
|
positionMouseDown.clientX - event.clientX === 0
|
|
@@ -356,7 +355,9 @@ export default class MinimapPlugin {
|
|
|
356
355
|
this.moveOverviewRegion(
|
|
357
356
|
this.wavesurfer.drawer.wrapper.scrollLeft / this.ratio
|
|
358
357
|
);
|
|
359
|
-
this.
|
|
358
|
+
this.util.style(this.overviewRegion, {
|
|
359
|
+
width: this.overviewWidth + 'px'
|
|
360
|
+
});
|
|
360
361
|
}
|
|
361
362
|
}
|
|
362
363
|
|
|
@@ -372,7 +373,9 @@ export default class MinimapPlugin {
|
|
|
372
373
|
this.overviewPosition =
|
|
373
374
|
this.drawer.container.offsetWidth - this.overviewWidth;
|
|
374
375
|
}
|
|
375
|
-
this.
|
|
376
|
+
this.util.style(this.overviewRegion, {
|
|
377
|
+
left: this.overviewPosition + 'px'
|
|
378
|
+
});
|
|
376
379
|
if (this.draggingOverview) {
|
|
377
380
|
this.wavesurfer.drawer.wrapper.scrollLeft =
|
|
378
381
|
this.overviewPosition * this.ratio;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @property {?number} snapToGridInterval Snap the regions to a grid of the specified multiples in seconds
|
|
11
11
|
* @property {?number} snapToGridOffset Shift the snap-to-grid by the specified seconds. May also be negative.
|
|
12
12
|
* @property {?boolean} deferInit Set to true to manually call
|
|
13
|
-
* @property {number
|
|
13
|
+
* @property {number} maxRegions Maximum number of regions that may be created by the user at one time.
|
|
14
14
|
* `initPlugin('regions')`
|
|
15
15
|
* @property {function} formatTimeCallback Allows custom formating for region tooltip.
|
|
16
16
|
* @property {?number} edgeScrollWidth='5% from container edges' Optional width for edgeScroll to start
|
|
@@ -129,15 +129,14 @@ export default class RegionsPlugin {
|
|
|
129
129
|
this.wavesurfer.Region = Region;
|
|
130
130
|
|
|
131
131
|
// By default, scroll the container if the user drags a region
|
|
132
|
-
// within 5% of its edge
|
|
132
|
+
// within 5% (based on its initial size) of its edge
|
|
133
133
|
const scrollWidthProportion = 0.05;
|
|
134
134
|
this._onBackendCreated = () => {
|
|
135
135
|
this.wrapper = this.wavesurfer.drawer.wrapper;
|
|
136
136
|
this.orientation = this.wavesurfer.drawer.orientation;
|
|
137
|
+
this.defaultEdgeScrollWidth = this.wrapper.clientWidth * scrollWidthProportion;
|
|
137
138
|
if (this.params.regions) {
|
|
138
139
|
this.params.regions.forEach(region => {
|
|
139
|
-
region.edgeScrollWidth = this.params.edgeScrollWidth ||
|
|
140
|
-
this.wrapper.clientWidth * scrollWidthProportion;
|
|
141
140
|
this.add(region);
|
|
142
141
|
});
|
|
143
142
|
}
|
|
@@ -171,6 +170,11 @@ export default class RegionsPlugin {
|
|
|
171
170
|
destroy() {
|
|
172
171
|
this.wavesurfer.un('ready', this._onReady);
|
|
173
172
|
this.wavesurfer.un('backend-created', this._onBackendCreated);
|
|
173
|
+
// Disabling `region-removed' because destroying the plugin calls
|
|
174
|
+
// the Region.remove() method that is also used to remove regions based
|
|
175
|
+
// on user input. This can cause confusion since teardown is not a
|
|
176
|
+
// user event, but would emit `region-removed` as if it was.
|
|
177
|
+
this.wavesurfer.setDisabledEventEmissions(['region-removed']);
|
|
174
178
|
this.disableDragSelection();
|
|
175
179
|
this.clear();
|
|
176
180
|
}
|
|
@@ -197,6 +201,16 @@ export default class RegionsPlugin {
|
|
|
197
201
|
return null;
|
|
198
202
|
}
|
|
199
203
|
|
|
204
|
+
params = {
|
|
205
|
+
edgeScrollWidth: this.params.edgeScrollWidth || this.defaultEdgeScrollWidth,
|
|
206
|
+
...params
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Take formatTimeCallback from plugin params if not already set
|
|
210
|
+
if (!params.formatTimeCallback && this.params.formatTimeCallback) {
|
|
211
|
+
params = {...params, formatTimeCallback: this.params.formatTimeCallback};
|
|
212
|
+
}
|
|
213
|
+
|
|
200
214
|
if (!params.minLength && this.regionsMinLength) {
|
|
201
215
|
params = {...params, minLength: this.regionsMinLength};
|
|
202
216
|
}
|
|
@@ -284,6 +298,19 @@ export default class RegionsPlugin {
|
|
|
284
298
|
this.vertical
|
|
285
299
|
);
|
|
286
300
|
|
|
301
|
+
// set the region channel index based on the clicked area
|
|
302
|
+
if (this.wavesurfer.params.splitChannels) {
|
|
303
|
+
const y = (e.touches ? e.touches[0].clientY : e.clientY) - wrapperRect.top;
|
|
304
|
+
const channelCount = this.wavesurfer.backend.buffer != null ? this.wavesurfer.backend.buffer.numberOfChannels : 1;
|
|
305
|
+
const channelHeight = this.wrapper.clientHeight / channelCount;
|
|
306
|
+
const channelIdx = Math.floor(y / channelHeight);
|
|
307
|
+
params.channelIdx = channelIdx;
|
|
308
|
+
const channelColors = this.wavesurfer.params.splitChannelsOptions.channelColors[channelIdx];
|
|
309
|
+
if (channelColors && channelColors.dragColor) {
|
|
310
|
+
params.color = channelColors.dragColor;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
287
314
|
drag = true;
|
|
288
315
|
start = this.wavesurfer.drawer.handleEvent(e, true);
|
|
289
316
|
region = null;
|
|
@@ -62,6 +62,7 @@ export class Region {
|
|
|
62
62
|
// select channel ID to set region
|
|
63
63
|
let channelIdx =
|
|
64
64
|
params.channelIdx == null ? -1 : parseInt(params.channelIdx);
|
|
65
|
+
this.channelIdx = channelIdx;
|
|
65
66
|
this.regionHeight = '100%';
|
|
66
67
|
this.marginTop = '0px';
|
|
67
68
|
|
|
@@ -87,7 +88,7 @@ export class Region {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
/* Update region params. */
|
|
90
|
-
update(params) {
|
|
91
|
+
update(params, eventParams) {
|
|
91
92
|
if (params.start != null) {
|
|
92
93
|
this.start = Number(params.start);
|
|
93
94
|
}
|
|
@@ -125,7 +126,7 @@ export class Region {
|
|
|
125
126
|
|
|
126
127
|
this.updateRender();
|
|
127
128
|
this.fireEvent('update');
|
|
128
|
-
this.wavesurfer.fireEvent('region-updated', this);
|
|
129
|
+
this.wavesurfer.fireEvent('region-updated', this, eventParams);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
/* Remove a single region. */
|
|
@@ -190,7 +191,7 @@ export class Region {
|
|
|
190
191
|
|
|
191
192
|
this.style(this.element, {
|
|
192
193
|
position: 'absolute',
|
|
193
|
-
zIndex:
|
|
194
|
+
zIndex: 3,
|
|
194
195
|
height: this.regionHeight,
|
|
195
196
|
top: this.marginTop
|
|
196
197
|
});
|
|
@@ -708,10 +709,33 @@ export class Region {
|
|
|
708
709
|
delta = this.start * -1;
|
|
709
710
|
}
|
|
710
711
|
|
|
712
|
+
const eventParams = {
|
|
713
|
+
direction: this._getDragDirection(delta),
|
|
714
|
+
action: 'drag'
|
|
715
|
+
};
|
|
716
|
+
|
|
711
717
|
this.update({
|
|
712
718
|
start: this.start + delta,
|
|
713
719
|
end: this.end + delta
|
|
714
|
-
});
|
|
720
|
+
}, eventParams);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Returns the direction of dragging region based on delta
|
|
725
|
+
* Negative delta means region is moving to the left
|
|
726
|
+
* Positive - to the right
|
|
727
|
+
* For zero delta the direction is not defined
|
|
728
|
+
* @param {number} delta Drag offset
|
|
729
|
+
* @returns {string|null} Direction 'left', 'right' or null
|
|
730
|
+
*/
|
|
731
|
+
_getDragDirection(delta) {
|
|
732
|
+
if (delta < 0) {
|
|
733
|
+
return 'left';
|
|
734
|
+
}
|
|
735
|
+
if (delta > 0) {
|
|
736
|
+
return 'right';
|
|
737
|
+
}
|
|
738
|
+
return null;
|
|
715
739
|
}
|
|
716
740
|
|
|
717
741
|
/**
|
|
@@ -724,13 +748,22 @@ export class Region {
|
|
|
724
748
|
*/
|
|
725
749
|
onResize(delta, direction) {
|
|
726
750
|
const duration = this.wavesurfer.getDuration();
|
|
751
|
+
const eventParams = {
|
|
752
|
+
action: 'resize',
|
|
753
|
+
direction: direction === 'start' ? 'left' : 'right'
|
|
754
|
+
};
|
|
755
|
+
|
|
727
756
|
if (direction === 'start') {
|
|
728
757
|
// Check if changing the start by the given delta would result in the region being smaller than minLength
|
|
729
|
-
// Ignore cases where we are making the region wider rather than shrinking it
|
|
730
758
|
if (delta > 0 && this.end - (this.start + delta) < this.minLength) {
|
|
731
759
|
delta = this.end - this.minLength - this.start;
|
|
732
760
|
}
|
|
733
761
|
|
|
762
|
+
// Check if changing the start by the given delta would result in the region being larger than maxLength
|
|
763
|
+
if (delta < 0 && this.end - (this.start + delta) > this.maxLength) {
|
|
764
|
+
delta = this.end - this.start - this.maxLength;
|
|
765
|
+
}
|
|
766
|
+
|
|
734
767
|
if (delta < 0 && (this.start + delta) < 0) {
|
|
735
768
|
delta = this.start * -1;
|
|
736
769
|
}
|
|
@@ -738,14 +771,18 @@ export class Region {
|
|
|
738
771
|
this.update({
|
|
739
772
|
start: Math.min(this.start + delta, this.end),
|
|
740
773
|
end: Math.max(this.start + delta, this.end)
|
|
741
|
-
});
|
|
774
|
+
}, eventParams);
|
|
742
775
|
} else {
|
|
743
776
|
// Check if changing the end by the given delta would result in the region being smaller than minLength
|
|
744
|
-
// Ignore cases where we are making the region wider rather than shrinking it
|
|
745
777
|
if (delta < 0 && this.end + delta - this.start < this.minLength) {
|
|
746
778
|
delta = this.start + this.minLength - this.end;
|
|
747
779
|
}
|
|
748
780
|
|
|
781
|
+
// Check if changing the end by the given delta would result in the region being larger than maxLength
|
|
782
|
+
if (delta > 0 && this.end + delta - this.start > this.maxLength) {
|
|
783
|
+
delta = this.maxLength - (this.end - this.start);
|
|
784
|
+
}
|
|
785
|
+
|
|
749
786
|
if (delta > 0 && (this.end + delta) > duration) {
|
|
750
787
|
delta = duration - this.end;
|
|
751
788
|
}
|
|
@@ -753,7 +790,7 @@ export class Region {
|
|
|
753
790
|
this.update({
|
|
754
791
|
start: Math.min(this.end + delta, this.start),
|
|
755
792
|
end: Math.max(this.end + delta, this.start)
|
|
756
|
-
});
|
|
793
|
+
}, eventParams);
|
|
757
794
|
}
|
|
758
795
|
}
|
|
759
796
|
|
|
@@ -8,6 +8,8 @@ import FFT from './fft';
|
|
|
8
8
|
* which to render
|
|
9
9
|
* @property {number} fftSamples=512 Number of samples to fetch to FFT. Must be
|
|
10
10
|
* a power of 2.
|
|
11
|
+
* @property {boolean} splitChannels=false Render with separate spectrograms for
|
|
12
|
+
* the channels of the audio
|
|
11
13
|
* @property {boolean} labels Set to true to display frequency labels.
|
|
12
14
|
* @property {number} noverlap Size of the overlapping window. Must be <
|
|
13
15
|
* fftSamples. Auto deduced from canvas size by default.
|
|
@@ -127,6 +129,8 @@ export default class SpectrogramPlugin {
|
|
|
127
129
|
this.noverlap = params.noverlap;
|
|
128
130
|
this.windowFunc = params.windowFunc;
|
|
129
131
|
this.alpha = params.alpha;
|
|
132
|
+
this.splitChannels = params.splitChannels;
|
|
133
|
+
this.channels = this.splitChannels ? ws.backend.buffer.numberOfChannels : 1;
|
|
130
134
|
|
|
131
135
|
this.createWrapper();
|
|
132
136
|
this.createCanvas();
|
|
@@ -176,7 +180,7 @@ export default class SpectrogramPlugin {
|
|
|
176
180
|
left: 0,
|
|
177
181
|
position: 'absolute',
|
|
178
182
|
zIndex: 9,
|
|
179
|
-
height: `${this.height / this.pixelRatio}px`,
|
|
183
|
+
height: `${this.height * this.channels / this.pixelRatio}px`,
|
|
180
184
|
width: `${55 / this.pixelRatio}px`
|
|
181
185
|
});
|
|
182
186
|
this.wrapper.appendChild(labelsEl);
|
|
@@ -197,7 +201,7 @@ export default class SpectrogramPlugin {
|
|
|
197
201
|
position: 'relative',
|
|
198
202
|
userSelect: 'none',
|
|
199
203
|
webkitUserSelect: 'none',
|
|
200
|
-
height: `${this.height / this.pixelRatio}px`
|
|
204
|
+
height: `${this.height * this.channels / this.pixelRatio}px`
|
|
201
205
|
});
|
|
202
206
|
|
|
203
207
|
if (wsParams.fillParent || wsParams.scrollParent) {
|
|
@@ -244,57 +248,58 @@ export default class SpectrogramPlugin {
|
|
|
244
248
|
updateCanvasStyle() {
|
|
245
249
|
const width = Math.round(this.width / this.pixelRatio) + 'px';
|
|
246
250
|
this.canvas.width = this.width;
|
|
247
|
-
this.canvas.height = this.height;
|
|
251
|
+
this.canvas.height = this.height * this.channels;
|
|
248
252
|
this.canvas.style.width = width;
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
drawSpectrogram(frequenciesData, my) {
|
|
256
|
+
if (!isNaN(frequenciesData[0][0])) { // data is 1ch [sample, freq] format
|
|
257
|
+
// to [channel, sample, freq] format
|
|
258
|
+
frequenciesData = [frequenciesData];
|
|
259
|
+
}
|
|
260
|
+
|
|
252
261
|
const spectrCc = my.spectrCc;
|
|
253
262
|
const height = my.height;
|
|
254
263
|
const width = my.width;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
264
|
+
|
|
265
|
+
if (!spectrCc) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (let c = 0; c < frequenciesData.length; c++) { // for each channel
|
|
270
|
+
const pixels = my.resample(frequenciesData[c]);
|
|
258
271
|
const imageData = spectrCc.createImageData(width, height);
|
|
259
|
-
let i;
|
|
260
|
-
let j;
|
|
261
|
-
let k;
|
|
262
272
|
|
|
263
|
-
for (i = 0; i < pixels.length; i++) {
|
|
264
|
-
for (j = 0; j < pixels[i].length; j++) {
|
|
273
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
274
|
+
for (let j = 0; j < pixels[i].length; j++) {
|
|
265
275
|
const colorMap = my.colorMap[pixels[i][j]];
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
const redIndex = y * (width * 4) + i * 4;
|
|
273
|
-
imageData.data[redIndex] = colorMap[0] * 255;
|
|
274
|
-
imageData.data[redIndex + 1] = colorMap[1] * 255;
|
|
275
|
-
imageData.data[redIndex + 2] = colorMap[2] * 255;
|
|
276
|
-
imageData.data[redIndex + 3] = colorMap[3] * 255;
|
|
277
|
-
}
|
|
278
|
-
/* eslint-enable max-depth */
|
|
276
|
+
const redIndex = ((height - j) * width + i) * 4;
|
|
277
|
+
imageData.data[redIndex] = colorMap[0] * 255;
|
|
278
|
+
imageData.data[redIndex + 1] = colorMap[1] * 255;
|
|
279
|
+
imageData.data[redIndex + 2] = colorMap[2] * 255;
|
|
280
|
+
imageData.data[redIndex + 3] = colorMap[3] * 255;
|
|
279
281
|
}
|
|
280
282
|
}
|
|
281
|
-
|
|
283
|
+
|
|
284
|
+
// stack spectrograms
|
|
285
|
+
spectrCc.putImageData(imageData, 0, height * c);
|
|
282
286
|
}
|
|
283
287
|
}
|
|
284
288
|
|
|
285
289
|
getFrequencies(callback) {
|
|
286
290
|
const fftSamples = this.fftSamples;
|
|
287
291
|
const buffer = (this.buffer = this.wavesurfer.backend.buffer);
|
|
288
|
-
const
|
|
289
|
-
const bufferLength = buffer.length;
|
|
290
|
-
const sampleRate = buffer.sampleRate;
|
|
291
|
-
const frequencies = [];
|
|
292
|
+
const channels = this.channels;
|
|
292
293
|
|
|
293
294
|
if (!buffer) {
|
|
294
295
|
this.fireEvent('error', 'Web Audio buffer is not available');
|
|
295
296
|
return;
|
|
296
297
|
}
|
|
297
298
|
|
|
299
|
+
// This may differ from file samplerate. Browser resamples audio.
|
|
300
|
+
const sampleRate = buffer.sampleRate;
|
|
301
|
+
const frequencies = [];
|
|
302
|
+
|
|
298
303
|
let noverlap = this.noverlap;
|
|
299
304
|
if (!noverlap) {
|
|
300
305
|
const uniqueSamplesPerPx = buffer.length / this.canvas.width;
|
|
@@ -307,24 +312,30 @@ export default class SpectrogramPlugin {
|
|
|
307
312
|
this.windowFunc,
|
|
308
313
|
this.alpha
|
|
309
314
|
);
|
|
310
|
-
const maxSlicesCount = Math.floor(
|
|
311
|
-
bufferLength / (fftSamples - noverlap)
|
|
312
|
-
);
|
|
313
|
-
let currentOffset = 0;
|
|
314
315
|
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
316
|
+
for (let c = 0; c < channels; c++) { // for each channel
|
|
317
|
+
const channelData = buffer.getChannelData(c);
|
|
318
|
+
const channelFreq = [];
|
|
319
|
+
let currentOffset = 0;
|
|
320
|
+
|
|
321
|
+
while (currentOffset + fftSamples < channelData.length) {
|
|
322
|
+
const segment = channelData.slice(
|
|
323
|
+
currentOffset,
|
|
324
|
+
currentOffset + fftSamples
|
|
325
|
+
);
|
|
326
|
+
const spectrum = fft.calculateSpectrum(segment);
|
|
327
|
+
const array = new Uint8Array(fftSamples / 2);
|
|
328
|
+
let j;
|
|
329
|
+
for (j = 0; j < fftSamples / 2; j++) {
|
|
330
|
+
array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
|
|
331
|
+
}
|
|
332
|
+
channelFreq.push(array);
|
|
333
|
+
// channelFreq: [sample, freq]
|
|
334
|
+
|
|
335
|
+
currentOffset += fftSamples - noverlap;
|
|
325
336
|
}
|
|
326
|
-
frequencies.push(
|
|
327
|
-
|
|
337
|
+
frequencies.push(channelFreq);
|
|
338
|
+
// frequencies: [channel, sample, freq]
|
|
328
339
|
}
|
|
329
340
|
callback(frequencies, this);
|
|
330
341
|
}
|
|
@@ -377,13 +388,17 @@ export default class SpectrogramPlugin {
|
|
|
377
388
|
|
|
378
389
|
// prepare canvas element for labels
|
|
379
390
|
const ctx = this.labelsEl.getContext('2d');
|
|
380
|
-
this.labelsEl.height = this.height;
|
|
391
|
+
this.labelsEl.height = this.height * this.channels;
|
|
381
392
|
this.labelsEl.width = bgWidth;
|
|
382
393
|
|
|
383
|
-
if (ctx) {
|
|
394
|
+
if (!ctx) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
for (let c = 0; c < this.channels; c++) { // for each channel
|
|
384
399
|
// fill background
|
|
385
400
|
ctx.fillStyle = bgFill;
|
|
386
|
-
ctx.fillRect(0,
|
|
401
|
+
ctx.fillRect(0, c * getMaxY, bgWidth, (1 + c) * getMaxY);
|
|
387
402
|
ctx.fill();
|
|
388
403
|
let i;
|
|
389
404
|
|
|
@@ -403,7 +418,7 @@ export default class SpectrogramPlugin {
|
|
|
403
418
|
let y;
|
|
404
419
|
|
|
405
420
|
if (i == 0) {
|
|
406
|
-
y = getMaxY + i - 10;
|
|
421
|
+
y = (1 + c) * getMaxY + i - 10;
|
|
407
422
|
// unit label
|
|
408
423
|
ctx.fillStyle = textColorUnit;
|
|
409
424
|
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
@@ -413,7 +428,7 @@ export default class SpectrogramPlugin {
|
|
|
413
428
|
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
414
429
|
ctx.fillText(label, x, y);
|
|
415
430
|
} else {
|
|
416
|
-
y = getMaxY - i * 50 + yLabelOffset;
|
|
431
|
+
y = (1 + c) * getMaxY - i * 50 + yLabelOffset;
|
|
417
432
|
// unit label
|
|
418
433
|
ctx.fillStyle = textColorUnit;
|
|
419
434
|
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
@@ -505,18 +505,20 @@ export default class TimelinePlugin {
|
|
|
505
505
|
|
|
506
506
|
this.canvases.forEach(canvas => {
|
|
507
507
|
const context = canvas.getContext('2d');
|
|
508
|
-
|
|
508
|
+
if (context) {
|
|
509
|
+
const canvasWidth = context.canvas.width;
|
|
509
510
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
511
|
+
if (xOffset > x + textWidth) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
513
514
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
515
|
+
if (xOffset + canvasWidth > x && context) {
|
|
516
|
+
textWidth = context.measureText(text).width;
|
|
517
|
+
context.fillText(text, x - xOffset, y);
|
|
518
|
+
}
|
|
518
519
|
|
|
519
|
-
|
|
520
|
+
xOffset += canvasWidth;
|
|
521
|
+
}
|
|
520
522
|
});
|
|
521
523
|
}
|
|
522
524
|
|
package/src/util/index.js
CHANGED
|
@@ -11,3 +11,4 @@ export { default as preventClick } from './prevent-click';
|
|
|
11
11
|
export { default as fetchFile } from './fetch';
|
|
12
12
|
export { default as clamp } from './clamp';
|
|
13
13
|
export { default as withOrientation } from './orientation';
|
|
14
|
+
export { default as ignoreSilenceMode } from './silence-mode';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ignores device silence mode when using the `WebAudio` backend.
|
|
3
|
+
*
|
|
4
|
+
* Many mobile devices contain a hardware button to mute the ringtone for incoming
|
|
5
|
+
* calls and messages. Unfortunately, on some platforms like iOS, this also mutes
|
|
6
|
+
* wavesurfer's audio when using the `WebAudio` backend. This function creates a
|
|
7
|
+
* temporary `<audio>` element that makes sure the WebAudio backend keeps playing
|
|
8
|
+
* when muting the device ringer.
|
|
9
|
+
*
|
|
10
|
+
* @since 5.2.0
|
|
11
|
+
*/
|
|
12
|
+
export default function ignoreSilenceMode() {
|
|
13
|
+
// Set the src to a short bit of url encoded as a silent mp3
|
|
14
|
+
// NOTE The silence MP3 must be high quality, when web audio sounds are played
|
|
15
|
+
// in parallel the web audio sound is mixed to match the bitrate of the html sound
|
|
16
|
+
// 0.01 seconds of silence VBR220-260 Joint Stereo 859B
|
|
17
|
+
const audioData = "data:audio/mpeg;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAACcQCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA//////////////////////////////////////////////////////////////////8AAABhTEFNRTMuMTAwA8MAAAAAAAAAABQgJAUHQQAB9AAAAnGMHkkIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//sQxAADgnABGiAAQBCqgCRMAAgEAH///////////////7+n/9FTuQsQH//////2NG0jWUGlio5gLQTOtIoeR2WX////X4s9Atb/JRVCbBUpeRUq//////////////////9RUi0f2jn/+xDECgPCjAEQAABN4AAANIAAAAQVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==";
|
|
18
|
+
|
|
19
|
+
// disable iOS Airplay (setting the attribute in js doesn't work)
|
|
20
|
+
let tmp = document.createElement("div");
|
|
21
|
+
tmp.innerHTML = '<audio x-webkit-airplay="deny"></audio>';
|
|
22
|
+
|
|
23
|
+
let audioSilentMode = tmp.children.item(0);
|
|
24
|
+
audioSilentMode.src = audioData;
|
|
25
|
+
audioSilentMode.preload = "auto";
|
|
26
|
+
audioSilentMode.type = "audio/mpeg";
|
|
27
|
+
audioSilentMode.disableRemotePlayback = true;
|
|
28
|
+
|
|
29
|
+
// play
|
|
30
|
+
audioSilentMode.play();
|
|
31
|
+
|
|
32
|
+
// cleanup
|
|
33
|
+
audioSilentMode.remove();
|
|
34
|
+
tmp.remove();
|
|
35
|
+
}
|