wavesurfer.js 5.0.1 → 6.0.1

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 (66) hide show
  1. package/CHANGES.md +48 -1
  2. package/README.md +10 -0
  3. package/dist/plugin/wavesurfer.cursor.js +53 -51
  4. package/dist/plugin/wavesurfer.cursor.js.map +1 -1
  5. package/dist/plugin/wavesurfer.cursor.min.js +2 -2
  6. package/dist/plugin/wavesurfer.cursor.min.js.map +1 -1
  7. package/dist/plugin/wavesurfer.elan.js +10 -7
  8. package/dist/plugin/wavesurfer.elan.js.map +1 -1
  9. package/dist/plugin/wavesurfer.elan.min.js +2 -2
  10. package/dist/plugin/wavesurfer.elan.min.js.map +1 -1
  11. package/dist/plugin/wavesurfer.markers.js +151 -25
  12. package/dist/plugin/wavesurfer.markers.js.map +1 -1
  13. package/dist/plugin/wavesurfer.markers.min.js +2 -2
  14. package/dist/plugin/wavesurfer.markers.min.js.map +1 -1
  15. package/dist/plugin/wavesurfer.mediasession.js +6 -6
  16. package/dist/plugin/wavesurfer.mediasession.js.map +1 -1
  17. package/dist/plugin/wavesurfer.mediasession.min.js +2 -2
  18. package/dist/plugin/wavesurfer.mediasession.min.js.map +1 -1
  19. package/dist/plugin/wavesurfer.microphone.js +5 -5
  20. package/dist/plugin/wavesurfer.microphone.js.map +1 -1
  21. package/dist/plugin/wavesurfer.microphone.min.js +2 -2
  22. package/dist/plugin/wavesurfer.microphone.min.js.map +1 -1
  23. package/dist/plugin/wavesurfer.minimap.js +26 -23
  24. package/dist/plugin/wavesurfer.minimap.js.map +1 -1
  25. package/dist/plugin/wavesurfer.minimap.min.js +2 -2
  26. package/dist/plugin/wavesurfer.minimap.min.js.map +1 -1
  27. package/dist/plugin/wavesurfer.playhead.js +5 -5
  28. package/dist/plugin/wavesurfer.playhead.js.map +1 -1
  29. package/dist/plugin/wavesurfer.playhead.min.js +2 -2
  30. package/dist/plugin/wavesurfer.playhead.min.js.map +1 -1
  31. package/dist/plugin/wavesurfer.regions.js +92 -23
  32. package/dist/plugin/wavesurfer.regions.js.map +1 -1
  33. package/dist/plugin/wavesurfer.regions.min.js +2 -2
  34. package/dist/plugin/wavesurfer.regions.min.js.map +1 -1
  35. package/dist/plugin/wavesurfer.spectrogram.js +104 -85
  36. package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
  37. package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
  38. package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
  39. package/dist/plugin/wavesurfer.timeline.js +67 -57
  40. package/dist/plugin/wavesurfer.timeline.js.map +1 -1
  41. package/dist/plugin/wavesurfer.timeline.min.js +2 -2
  42. package/dist/plugin/wavesurfer.timeline.min.js.map +1 -1
  43. package/dist/wavesurfer-html-init.js +6 -6
  44. package/dist/wavesurfer-html-init.js.map +1 -1
  45. package/dist/wavesurfer-html-init.min.js +2 -2
  46. package/dist/wavesurfer-html-init.min.js.map +1 -1
  47. package/dist/wavesurfer.js +299 -133
  48. package/dist/wavesurfer.js.map +1 -1
  49. package/dist/wavesurfer.min.js +2 -2
  50. package/dist/wavesurfer.min.js.map +1 -1
  51. package/package.json +24 -24
  52. package/src/drawer.canvasentry.js +30 -6
  53. package/src/drawer.js +1 -0
  54. package/src/drawer.multicanvas.js +19 -8
  55. package/src/mediaelement.js +1 -1
  56. package/src/plugin/cursor/index.js +68 -70
  57. package/src/plugin/markers/index.js +120 -23
  58. package/src/plugin/minimap/index.js +24 -21
  59. package/src/plugin/regions/index.js +30 -3
  60. package/src/plugin/regions/region.js +47 -10
  61. package/src/plugin/spectrogram/index.js +101 -82
  62. package/src/plugin/timeline/index.js +30 -19
  63. package/src/util/index.js +1 -0
  64. package/src/util/silence-mode.js +35 -0
  65. package/src/wavesurfer.js +54 -23
  66. 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.moveOverviewRegion(e.target.scrollLeft / this.ratio);
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.style(
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', event => {
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', event => {
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
- this.drawer.container.getBoundingClientRect().left -
322
- relativePositionX
319
+ this.drawer.container.getBoundingClientRect().left -
320
+ relativePositionX
323
321
  );
324
322
  }
325
323
  });
326
324
 
327
- this.drawer.wrapper.addEventListener('mouseup', event => {
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.overviewRegion.style.width = this.overviewWidth + 'px';
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.overviewRegion.style.left = this.overviewPosition + 'px';
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: 2,
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
- for (i = 0; i < pixels.length; i++) {
263
- for (j = 0; j < pixels[i].length; j++) {
264
- const colorMap = my.colorMap[pixels[i][j]];
265
- /* eslint-disable max-depth */
266
- for (k = 0; k < heightFactor; k++) {
267
- let y = height - j * heightFactor;
268
- if (heightFactor === 2 && k === 1) {
269
- y--;
270
- }
271
- const redIndex = y * (width * 4) + i * 4;
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 channelOne = buffer.getChannelData(0);
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
- while (currentOffset + fftSamples < channelOne.length) {
314
- const segment = channelOne.slice(
315
- currentOffset,
316
- currentOffset + fftSamples
317
- );
318
- const spectrum = fft.calculateSpectrum(segment);
319
- const array = new Uint8Array(fftSamples / 2);
320
- let j;
321
- for (j = 0; j < fftSamples / 2; j++) {
322
- array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
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(array);
325
- currentOffset += fftSamples - noverlap;
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
- // fill background
382
- ctx.fillStyle = bgFill;
383
- ctx.fillRect(0, 0, bgWidth, getMaxY);
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
- const freq = freqStart + step * i;
393
- const index = Math.round(
394
- (freq / (this.sampleRate / 2)) * this.fftSamples
395
- );
396
- const label = this.freqType(freq);
397
- const units = this.unitType(freq);
398
- const yLabelOffset = 2;
399
- const x = 16;
400
- let y;
401
-
402
- if (i == 0) {
403
- y = getMaxY + i - 10;
404
- // unit label
405
- ctx.fillStyle = textColorUnit;
406
- ctx.font = fontSizeUnit + ' ' + fontType;
407
- ctx.fillText(units, x + 24, y);
408
- // freq label
409
- ctx.fillStyle = textColorFreq;
410
- ctx.font = fontSizeFreq + ' ' + fontType;
411
- ctx.fillText(label, x, y);
412
- } else {
413
- y = getMaxY - i * 50 + yLabelOffset;
414
- // unit label
415
- ctx.fillStyle = textColorUnit;
416
- ctx.font = fontSizeUnit + ' ' + fontType;
417
- ctx.fillText(units, x + 24, y);
418
- // freq label
419
- ctx.fillStyle = textColorFreq;
420
- ctx.font = fontSizeFreq + ' ' + fontType;
421
- ctx.fillText(label, x, y);
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').fillStyle = fillStyle;
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').font = font;
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
- .fillRect(
477
- intersection.x1 - leftOffset,
478
- intersection.y1,
479
- intersection.x2 - intersection.x1,
480
- intersection.y2 - intersection.y1
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
- const canvasWidth = context.canvas.width;
508
+ if (context) {
509
+ const canvasWidth = context.canvas.width;
500
510
 
501
- if (xOffset > x + textWidth) {
502
- return;
503
- }
511
+ if (xOffset > x + textWidth) {
512
+ return;
513
+ }
504
514
 
505
- if (xOffset + canvasWidth > x) {
506
- textWidth = context.measureText(text).width;
507
- context.fillText(text, x - xOffset, y);
508
- }
515
+ if (xOffset + canvasWidth > x && context) {
516
+ textWidth = context.measureText(text).width;
517
+ context.fillText(text, x - xOffset, y);
518
+ }
509
519
 
510
- xOffset += canvasWidth;
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';