wavesurfer.js 5.0.0 → 6.0.0
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 +48 -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 +151 -25
- 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 +92 -23
- 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 +104 -85
- 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 +67 -57
- 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 +300 -134
- 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 +24 -24
- package/src/drawer.canvasentry.js +30 -6
- package/src/drawer.js +2 -1
- 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 +120 -23
- package/src/plugin/minimap/index.js +24 -21
- package/src/plugin/regions/index.js +30 -3
- package/src/plugin/regions/region.js +47 -10
- package/src/plugin/spectrogram/index.js +101 -82
- package/src/plugin/timeline/index.js +30 -19
- 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;
|
|
@@ -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
|
});
|
|
@@ -211,7 +212,7 @@ export class Region {
|
|
|
211
212
|
|
|
212
213
|
// Default CSS properties for both handles.
|
|
213
214
|
const css = {
|
|
214
|
-
cursor: 'row-resize',
|
|
215
|
+
cursor: this.vertical ? 'row-resize' : 'col-resize',
|
|
215
216
|
position: 'absolute',
|
|
216
217
|
top: '0px',
|
|
217
218
|
width: '2px',
|
|
@@ -680,7 +681,7 @@ export class Region {
|
|
|
680
681
|
this.element.addEventListener('touchstart', onDown);
|
|
681
682
|
|
|
682
683
|
document.body.addEventListener('mousemove', onMove);
|
|
683
|
-
document.body.addEventListener('touchmove', onMove);
|
|
684
|
+
document.body.addEventListener('touchmove', onMove, {passive: false});
|
|
684
685
|
|
|
685
686
|
document.addEventListener('mouseup', onUp);
|
|
686
687
|
document.body.addEventListener('touchend', onUp);
|
|
@@ -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,55 +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
|
-
const pixels = my.resample(frequenciesData);
|
|
256
|
-
const heightFactor = my.buffer ? 2 / my.buffer.numberOfChannels : 1;
|
|
257
|
-
const imageData = spectrCc.createImageData(width, height);
|
|
258
|
-
let i;
|
|
259
|
-
let j;
|
|
260
|
-
let k;
|
|
261
264
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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]);
|
|
271
|
+
const imageData = spectrCc.createImageData(width, height);
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < pixels.length; i++) {
|
|
274
|
+
for (let j = 0; j < pixels[i].length; j++) {
|
|
275
|
+
const colorMap = my.colorMap[pixels[i][j]];
|
|
276
|
+
const redIndex = ((height - j) * width + i) * 4;
|
|
272
277
|
imageData.data[redIndex] = colorMap[0] * 255;
|
|
273
278
|
imageData.data[redIndex + 1] = colorMap[1] * 255;
|
|
274
279
|
imageData.data[redIndex + 2] = colorMap[2] * 255;
|
|
275
280
|
imageData.data[redIndex + 3] = colorMap[3] * 255;
|
|
276
281
|
}
|
|
277
|
-
/* eslint-enable max-depth */
|
|
278
282
|
}
|
|
283
|
+
|
|
284
|
+
// stack spectrograms
|
|
285
|
+
spectrCc.putImageData(imageData, 0, height * c);
|
|
279
286
|
}
|
|
280
|
-
spectrCc.putImageData(imageData, 0, 0);
|
|
281
287
|
}
|
|
282
288
|
|
|
283
289
|
getFrequencies(callback) {
|
|
284
290
|
const fftSamples = this.fftSamples;
|
|
285
291
|
const buffer = (this.buffer = this.wavesurfer.backend.buffer);
|
|
286
|
-
const
|
|
287
|
-
const bufferLength = buffer.length;
|
|
288
|
-
const sampleRate = buffer.sampleRate;
|
|
289
|
-
const frequencies = [];
|
|
292
|
+
const channels = this.channels;
|
|
290
293
|
|
|
291
294
|
if (!buffer) {
|
|
292
295
|
this.fireEvent('error', 'Web Audio buffer is not available');
|
|
293
296
|
return;
|
|
294
297
|
}
|
|
295
298
|
|
|
299
|
+
// This may differ from file samplerate. Browser resamples audio.
|
|
300
|
+
const sampleRate = buffer.sampleRate;
|
|
301
|
+
const frequencies = [];
|
|
302
|
+
|
|
296
303
|
let noverlap = this.noverlap;
|
|
297
304
|
if (!noverlap) {
|
|
298
305
|
const uniqueSamplesPerPx = buffer.length / this.canvas.width;
|
|
@@ -305,24 +312,30 @@ export default class SpectrogramPlugin {
|
|
|
305
312
|
this.windowFunc,
|
|
306
313
|
this.alpha
|
|
307
314
|
);
|
|
308
|
-
const maxSlicesCount = Math.floor(
|
|
309
|
-
bufferLength / (fftSamples - noverlap)
|
|
310
|
-
);
|
|
311
|
-
let currentOffset = 0;
|
|
312
315
|
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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;
|
|
323
336
|
}
|
|
324
|
-
frequencies.push(
|
|
325
|
-
|
|
337
|
+
frequencies.push(channelFreq);
|
|
338
|
+
// frequencies: [channel, sample, freq]
|
|
326
339
|
}
|
|
327
340
|
callback(frequencies, this);
|
|
328
341
|
}
|
|
@@ -375,50 +388,56 @@ export default class SpectrogramPlugin {
|
|
|
375
388
|
|
|
376
389
|
// prepare canvas element for labels
|
|
377
390
|
const ctx = this.labelsEl.getContext('2d');
|
|
378
|
-
this.labelsEl.height = this.height;
|
|
391
|
+
this.labelsEl.height = this.height * this.channels;
|
|
379
392
|
this.labelsEl.width = bgWidth;
|
|
380
393
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
ctx.fill();
|
|
385
|
-
let i;
|
|
386
|
-
|
|
387
|
-
// render labels
|
|
388
|
-
for (i = 0; i <= labelIndex; i++) {
|
|
389
|
-
ctx.textAlign = textAlign;
|
|
390
|
-
ctx.textBaseline = 'middle';
|
|
394
|
+
if (!ctx) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
391
397
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
398
|
+
for (let c = 0; c < this.channels; c++) { // for each channel
|
|
399
|
+
// fill background
|
|
400
|
+
ctx.fillStyle = bgFill;
|
|
401
|
+
ctx.fillRect(0, c * getMaxY, bgWidth, (1 + c) * getMaxY);
|
|
402
|
+
ctx.fill();
|
|
403
|
+
let i;
|
|
404
|
+
|
|
405
|
+
// render labels
|
|
406
|
+
for (i = 0; i <= labelIndex; i++) {
|
|
407
|
+
ctx.textAlign = textAlign;
|
|
408
|
+
ctx.textBaseline = 'middle';
|
|
409
|
+
|
|
410
|
+
const freq = freqStart + step * i;
|
|
411
|
+
const index = Math.round(
|
|
412
|
+
(freq / (this.sampleRate / 2)) * this.fftSamples
|
|
413
|
+
);
|
|
414
|
+
const label = this.freqType(freq);
|
|
415
|
+
const units = this.unitType(freq);
|
|
416
|
+
const yLabelOffset = 2;
|
|
417
|
+
const x = 16;
|
|
418
|
+
let y;
|
|
419
|
+
|
|
420
|
+
if (i == 0) {
|
|
421
|
+
y = (1 + c) * getMaxY + i - 10;
|
|
422
|
+
// unit label
|
|
423
|
+
ctx.fillStyle = textColorUnit;
|
|
424
|
+
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
425
|
+
ctx.fillText(units, x + 24, y);
|
|
426
|
+
// freq label
|
|
427
|
+
ctx.fillStyle = textColorFreq;
|
|
428
|
+
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
429
|
+
ctx.fillText(label, x, y);
|
|
430
|
+
} else {
|
|
431
|
+
y = (1 + c) * getMaxY - i * 50 + yLabelOffset;
|
|
432
|
+
// unit label
|
|
433
|
+
ctx.fillStyle = textColorUnit;
|
|
434
|
+
ctx.font = fontSizeUnit + ' ' + fontType;
|
|
435
|
+
ctx.fillText(units, x + 24, y);
|
|
436
|
+
// freq label
|
|
437
|
+
ctx.fillStyle = textColorFreq;
|
|
438
|
+
ctx.font = fontSizeFreq + ' ' + fontType;
|
|
439
|
+
ctx.fillText(label, x, y);
|
|
440
|
+
}
|
|
422
441
|
}
|
|
423
442
|
}
|
|
424
443
|
}
|
|
@@ -434,7 +434,10 @@ export default class TimelinePlugin {
|
|
|
434
434
|
*/
|
|
435
435
|
setFillStyles(fillStyle) {
|
|
436
436
|
this.canvases.forEach(canvas => {
|
|
437
|
-
canvas.getContext('2d')
|
|
437
|
+
const context = canvas.getContext('2d');
|
|
438
|
+
if (context) {
|
|
439
|
+
context.fillStyle = fillStyle;
|
|
440
|
+
}
|
|
438
441
|
});
|
|
439
442
|
}
|
|
440
443
|
|
|
@@ -445,7 +448,10 @@ export default class TimelinePlugin {
|
|
|
445
448
|
*/
|
|
446
449
|
setFonts(font) {
|
|
447
450
|
this.canvases.forEach(canvas => {
|
|
448
|
-
canvas.getContext('2d')
|
|
451
|
+
const context = canvas.getContext('2d');
|
|
452
|
+
if (context) {
|
|
453
|
+
context.font = font;
|
|
454
|
+
}
|
|
449
455
|
});
|
|
450
456
|
}
|
|
451
457
|
|
|
@@ -471,14 +477,17 @@ export default class TimelinePlugin {
|
|
|
471
477
|
};
|
|
472
478
|
|
|
473
479
|
if (intersection.x1 < intersection.x2) {
|
|
474
|
-
canvas
|
|
475
|
-
.getContext('2d')
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
const context = canvas
|
|
481
|
+
.getContext('2d');
|
|
482
|
+
if (context) {
|
|
483
|
+
context
|
|
484
|
+
.fillRect(
|
|
485
|
+
intersection.x1 - leftOffset,
|
|
486
|
+
intersection.y1,
|
|
487
|
+
intersection.x2 - intersection.x1,
|
|
488
|
+
intersection.y2 - intersection.y1
|
|
489
|
+
);
|
|
490
|
+
}
|
|
482
491
|
}
|
|
483
492
|
});
|
|
484
493
|
}
|
|
@@ -496,18 +505,20 @@ export default class TimelinePlugin {
|
|
|
496
505
|
|
|
497
506
|
this.canvases.forEach(canvas => {
|
|
498
507
|
const context = canvas.getContext('2d');
|
|
499
|
-
|
|
508
|
+
if (context) {
|
|
509
|
+
const canvasWidth = context.canvas.width;
|
|
500
510
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
511
|
+
if (xOffset > x + textWidth) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
504
514
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
515
|
+
if (xOffset + canvasWidth > x && context) {
|
|
516
|
+
textWidth = context.measureText(text).width;
|
|
517
|
+
context.fillText(text, x - xOffset, y);
|
|
518
|
+
}
|
|
509
519
|
|
|
510
|
-
|
|
520
|
+
xOffset += canvasWidth;
|
|
521
|
+
}
|
|
511
522
|
});
|
|
512
523
|
}
|
|
513
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';
|