wavesurfer.js 5.2.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 +22 -0
- package/README.md +10 -0
- package/dist/plugin/wavesurfer.cursor.js +52 -50
- 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 +9 -6
- 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 +141 -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 +5 -5
- 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 +4 -4
- 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 +25 -22
- 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 +4 -4
- 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 +53 -16
- 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 +70 -57
- 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 +39 -44
- 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 +5 -5
- package/dist/wavesurfer-html-init.min.js +2 -2
- package/dist/wavesurfer-html-init.min.js.map +1 -1
- package/dist/wavesurfer.js +209 -121
- 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 +30 -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 +30 -3
- package/src/plugin/regions/region.js +13 -4
- package/src/plugin/spectrogram/index.js +65 -50
- package/src/wavesurfer.js +31 -10
|
@@ -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
|
|
|
@@ -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
|
});
|
|
@@ -749,16 +750,20 @@ export class Region {
|
|
|
749
750
|
const duration = this.wavesurfer.getDuration();
|
|
750
751
|
const eventParams = {
|
|
751
752
|
action: 'resize',
|
|
752
|
-
direction: direction === 'start' ? '
|
|
753
|
+
direction: direction === 'start' ? 'left' : 'right'
|
|
753
754
|
};
|
|
754
755
|
|
|
755
756
|
if (direction === 'start') {
|
|
756
757
|
// Check if changing the start by the given delta would result in the region being smaller than minLength
|
|
757
|
-
// Ignore cases where we are making the region wider rather than shrinking it
|
|
758
758
|
if (delta > 0 && this.end - (this.start + delta) < this.minLength) {
|
|
759
759
|
delta = this.end - this.minLength - this.start;
|
|
760
760
|
}
|
|
761
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
|
+
|
|
762
767
|
if (delta < 0 && (this.start + delta) < 0) {
|
|
763
768
|
delta = this.start * -1;
|
|
764
769
|
}
|
|
@@ -769,11 +774,15 @@ export class Region {
|
|
|
769
774
|
}, eventParams);
|
|
770
775
|
} else {
|
|
771
776
|
// Check if changing the end by the given delta would result in the region being smaller than minLength
|
|
772
|
-
// Ignore cases where we are making the region wider rather than shrinking it
|
|
773
777
|
if (delta < 0 && this.end + delta - this.start < this.minLength) {
|
|
774
778
|
delta = this.start + this.minLength - this.end;
|
|
775
779
|
}
|
|
776
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
|
+
|
|
777
786
|
if (delta > 0 && (this.end + delta) > duration) {
|
|
778
787
|
delta = duration - this.end;
|
|
779
788
|
}
|
|
@@ -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;
|
package/src/wavesurfer.js
CHANGED
|
@@ -69,6 +69,8 @@ import MediaElementWebAudio from './mediaelement-webaudio';
|
|
|
69
69
|
* pixels.
|
|
70
70
|
* @property {boolean} hideScrollbar=false Whether to hide the horizontal
|
|
71
71
|
* scrollbar when one would normally be shown.
|
|
72
|
+
* @property {boolean} hideCursor=false Whether to hide the mouse cursor
|
|
73
|
+
* when one would normally be shown by default.
|
|
72
74
|
* @property {boolean} ignoreSilenceMode=false If true, ignores device silence mode
|
|
73
75
|
* when using the `WebAudio` backend.
|
|
74
76
|
* @property {boolean} interact=true Whether the mouse interaction will be
|
|
@@ -270,6 +272,7 @@ export default class WaveSurfer extends util.Observer {
|
|
|
270
272
|
forceDecode: false,
|
|
271
273
|
height: 128,
|
|
272
274
|
hideScrollbar: false,
|
|
275
|
+
hideCursor: false,
|
|
273
276
|
ignoreSilenceMode: false,
|
|
274
277
|
interact: true,
|
|
275
278
|
loopSelection: true,
|
|
@@ -1125,40 +1128,58 @@ export default class WaveSurfer extends util.Observer {
|
|
|
1125
1128
|
/**
|
|
1126
1129
|
* Get the fill color of the waveform after the cursor.
|
|
1127
1130
|
*
|
|
1128
|
-
* @
|
|
1131
|
+
* @param {?number} channelIdx Optional index of the channel to get its wave color if splitChannels is true
|
|
1132
|
+
* @return {string|object} A CSS color string, or an array of CSS color strings.
|
|
1129
1133
|
*/
|
|
1130
|
-
getWaveColor() {
|
|
1134
|
+
getWaveColor(channelIdx = null) {
|
|
1135
|
+
if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
|
|
1136
|
+
return this.params.splitChannelsOptions.channelColors[channelIdx].waveColor;
|
|
1137
|
+
}
|
|
1131
1138
|
return this.params.waveColor;
|
|
1132
1139
|
}
|
|
1133
1140
|
|
|
1134
1141
|
/**
|
|
1135
1142
|
* Set the fill color of the waveform after the cursor.
|
|
1136
1143
|
*
|
|
1137
|
-
* @param {string} color A CSS color string.
|
|
1144
|
+
* @param {string|object} color A CSS color string, or an array of CSS color strings.
|
|
1145
|
+
* @param {?number} channelIdx Optional index of the channel to set its wave color if splitChannels is true
|
|
1138
1146
|
* @example wavesurfer.setWaveColor('#ddd');
|
|
1139
1147
|
*/
|
|
1140
|
-
setWaveColor(color) {
|
|
1141
|
-
this.params.
|
|
1148
|
+
setWaveColor(color, channelIdx = null) {
|
|
1149
|
+
if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
|
|
1150
|
+
this.params.splitChannelsOptions.channelColors[channelIdx].waveColor = color;
|
|
1151
|
+
} else {
|
|
1152
|
+
this.params.waveColor = color;
|
|
1153
|
+
}
|
|
1142
1154
|
this.drawBuffer();
|
|
1143
1155
|
}
|
|
1144
1156
|
|
|
1145
1157
|
/**
|
|
1146
1158
|
* Get the fill color of the waveform behind the cursor.
|
|
1147
1159
|
*
|
|
1148
|
-
* @
|
|
1160
|
+
* @param {?number} channelIdx Optional index of the channel to get its progress color if splitChannels is true
|
|
1161
|
+
* @return {string|object} A CSS color string, or an array of CSS color strings.
|
|
1149
1162
|
*/
|
|
1150
|
-
getProgressColor() {
|
|
1163
|
+
getProgressColor(channelIdx = null) {
|
|
1164
|
+
if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
|
|
1165
|
+
return this.params.splitChannelsOptions.channelColors[channelIdx].progressColor;
|
|
1166
|
+
}
|
|
1151
1167
|
return this.params.progressColor;
|
|
1152
1168
|
}
|
|
1153
1169
|
|
|
1154
1170
|
/**
|
|
1155
1171
|
* Set the fill color of the waveform behind the cursor.
|
|
1156
1172
|
*
|
|
1157
|
-
* @param {string} color A CSS color string.
|
|
1173
|
+
* @param {string|object} color A CSS color string, or an array of CSS color strings.
|
|
1174
|
+
* @param {?number} channelIdx Optional index of the channel to set its progress color if splitChannels is true
|
|
1158
1175
|
* @example wavesurfer.setProgressColor('#400');
|
|
1159
1176
|
*/
|
|
1160
|
-
setProgressColor(color) {
|
|
1161
|
-
this.params.
|
|
1177
|
+
setProgressColor(color, channelIdx) {
|
|
1178
|
+
if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
|
|
1179
|
+
this.params.splitChannelsOptions.channelColors[channelIdx].progressColor = color;
|
|
1180
|
+
} else {
|
|
1181
|
+
this.params.progressColor = color;
|
|
1182
|
+
}
|
|
1162
1183
|
this.drawBuffer();
|
|
1163
1184
|
}
|
|
1164
1185
|
|