wavesurfer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,390 @@
1
+ 'use strict';
2
+
3
+ /* Regions manager */
4
+ WaveSurfer.Regions = {
5
+ init: function (wavesurfer) {
6
+ this.wavesurfer = wavesurfer;
7
+ this.wrapper = this.wavesurfer.drawer.wrapper;
8
+
9
+ /* Id-based hash of regions. */
10
+ this.list = {};
11
+ },
12
+
13
+ /* Remove a region. */
14
+ add: function (params) {
15
+ var region = Object.create(WaveSurfer.Region);
16
+ region.init(params, this.wavesurfer);
17
+
18
+ this.list[region.id] = region;
19
+
20
+ region.on('remove', (function () {
21
+ delete this.list[region.id];
22
+ }).bind(this));
23
+
24
+ return region;
25
+ },
26
+
27
+ /* Remove all regions. */
28
+ clear: function () {
29
+ Object.keys(this.list).forEach(function (id) {
30
+ this.list[id].remove();
31
+ }, this);
32
+ },
33
+
34
+ enableDragSelection: function (params) {
35
+ var my = this;
36
+ var drag;
37
+ var start;
38
+ var region;
39
+
40
+ this.wrapper.addEventListener('mousedown', function (e) {
41
+ drag = true;
42
+ start = my.wavesurfer.drawer.handleEvent(e);
43
+ region = null;
44
+ });
45
+ this.wrapper.addEventListener('mouseup', function () {
46
+ drag = false;
47
+ region = null;
48
+ });
49
+ this.wrapper.addEventListener('mousemove', function (e) {
50
+ if (!drag) { return; }
51
+
52
+ if (!region) {
53
+ region = my.add(params || {});
54
+ }
55
+
56
+ var duration = my.wavesurfer.getDuration();
57
+ var end = my.wavesurfer.drawer.handleEvent(e);
58
+ region.update({
59
+ start: Math.min(end * duration, start * duration),
60
+ end: Math.max(end * duration, start * duration)
61
+ });
62
+ });
63
+ }
64
+ };
65
+
66
+ WaveSurfer.Region = {
67
+ /* Helper function to assign CSS styles. */
68
+ style: WaveSurfer.Drawer.style,
69
+
70
+ init: function (params, wavesurfer) {
71
+ this.wavesurfer = wavesurfer;
72
+ this.wrapper = wavesurfer.drawer.wrapper;
73
+
74
+ this.id = params.id == null ? WaveSurfer.util.getId() : params.id;
75
+ this.start = Number(params.start) || 0;
76
+ this.end = params.end == null ?
77
+ // small marker-like region
78
+ this.start + (4 / this.wrapper.scrollWidth) * this.wavesurfer.getDuration() :
79
+ Number(params.end);
80
+ this.resize = params.resize === undefined ? true : Boolean(params.resize);
81
+ this.drag = params.drag === undefined ? true : Boolean(params.drag);
82
+ this.loop = Boolean(params.loop);
83
+ this.color = params.color || 'rgba(0, 0, 0, 0.1)';
84
+ this.data = params.data || {};
85
+
86
+ this.bindInOut();
87
+ this.render();
88
+
89
+ this.wavesurfer.fireEvent('region-created', this);
90
+ },
91
+
92
+ /* Update region params. */
93
+ update: function (params) {
94
+ if (null != params.start) {
95
+ this.start = Number(params.start);
96
+ }
97
+ if (null != params.end) {
98
+ this.end = Number(params.end);
99
+ }
100
+ if (null != params.loop) {
101
+ this.loop = Boolean(params.loop);
102
+ }
103
+ if (null != params.color) {
104
+ this.color = params.color;
105
+ }
106
+ if (null != params.data) {
107
+ this.data = params.data;
108
+ }
109
+ if (null != params.resize) {
110
+ this.resize = Boolean(params.resize);
111
+ }
112
+ if (null != params.drag) {
113
+ this.drag = Boolean(params.drag);
114
+ }
115
+ this.updateRender();
116
+ this.fireEvent('update');
117
+ this.wavesurfer.fireEvent('region-updated', this);
118
+ },
119
+
120
+ /* Remove a single region. */
121
+ remove: function (region) {
122
+ if (this.element) {
123
+ this.wrapper.removeChild(this.element);
124
+ this.element = null;
125
+ this.fireEvent('remove');
126
+ this.wavesurfer.fireEvent('region-removed', this);
127
+ }
128
+ },
129
+
130
+ /* Play the audio region. */
131
+ play: function () {
132
+ this.wavesurfer.play(this.start, this.end);
133
+ this.fireEvent('play');
134
+ this.wavesurfer.fireEvent('region-play', this);
135
+ },
136
+
137
+ /* Play the region in loop. */
138
+ playLoop: function () {
139
+ this.play();
140
+ this.once('out', this.playLoop.bind(this));
141
+ },
142
+
143
+ /* Render a region as a DOM element. */
144
+ render: function () {
145
+ var regionEl = document.createElement('region');
146
+ regionEl.className = 'wavesurfer-region';
147
+ regionEl.title = this.formatTime(this.start, this.end);
148
+
149
+ var width = this.wrapper.scrollWidth;
150
+ this.style(regionEl, {
151
+ position: 'absolute',
152
+ zIndex: 2,
153
+ height: '100%',
154
+ top: '0px'
155
+ });
156
+
157
+ /* Resize handles */
158
+ if (this.resize) {
159
+ var handleLeft = regionEl.appendChild(document.createElement('handle'));
160
+ var handleRight = regionEl.appendChild(document.createElement('handle'));
161
+ handleLeft.className = 'wavesurfer-handle wavesurfer-handle-start';
162
+ handleRight.className = 'wavesurfer-handle wavesurfer-handle-end';
163
+ var css = {
164
+ cursor: 'col-resize',
165
+ position: 'absolute',
166
+ left: '0px',
167
+ top: '0px',
168
+ width: '1%',
169
+ maxWidth: '4px',
170
+ height: '100%'
171
+ };
172
+ this.style(handleLeft, css);
173
+ this.style(handleRight, css);
174
+ this.style(handleRight, {
175
+ left: '100%'
176
+ });
177
+ }
178
+
179
+ this.element = this.wrapper.appendChild(regionEl);
180
+ this.updateRender();
181
+ this.bindEvents(regionEl);
182
+ },
183
+
184
+ formatTime: function (start, end) {
185
+ return (start == end ? [ start ] : [ start, end ]).map(function (time) {
186
+ return [
187
+ Math.floor((time % 3600) / 60), // minutes
188
+ ('00' + Math.floor(time % 60)).slice(-2) // seconds
189
+ ].join(':');
190
+ }).join('–');
191
+ },
192
+
193
+ /* Update element's position, width, color. */
194
+ updateRender: function () {
195
+ var dur = this.wavesurfer.getDuration();
196
+ var width = this.wrapper.scrollWidth;
197
+
198
+ if (this.start < 0) {
199
+ this.start = 0;
200
+ this.end = this.end - this.start;
201
+ }
202
+ if (this.end > dur) {
203
+ this.end = dur;
204
+ this.start = dur - (this.end - this.start);
205
+ }
206
+ this.style(this.element, {
207
+ left: ~~(this.start / dur * width) + 'px',
208
+ width: ~~((this.end - this.start) / dur * width) + 'px',
209
+ backgroundColor: this.color,
210
+ cursor: this.drag ? 'move' : 'default'
211
+ });
212
+ this.element.title = this.formatTime(this.start, this.end);
213
+ },
214
+
215
+ /* Bind audio events. */
216
+ bindInOut: function () {
217
+ var my = this;
218
+
219
+ var onPlay = function () {
220
+ my.firedIn = false;
221
+ my.firedOut = false;
222
+ };
223
+
224
+ var onProcess = function (time) {
225
+ if (!my.firedIn && my.start <= time && my.end > time) {
226
+ my.firedIn = true;
227
+ my.fireEvent('in');
228
+ my.wavesurfer.fireEvent('region-in', my);
229
+ }
230
+ if (!my.firedOut && my.firedIn && my.end <= time) {
231
+ my.firedOut = true;
232
+ my.fireEvent('out');
233
+ my.wavesurfer.fireEvent('region-out', my);
234
+ }
235
+ };
236
+
237
+ this.wavesurfer.on('play', onPlay);
238
+ this.wavesurfer.backend.on('audioprocess', onProcess);
239
+
240
+ this.on('remove', function () {
241
+ my.wavesurfer.un('play', onPlay);
242
+ my.wavesurfer.backend.un('audioprocess', onProcess);
243
+ });
244
+
245
+ /* Loop playback. */
246
+ this.on('out', function () {
247
+ if (my.loop) {
248
+ my.wavesurfer.play(my.start);
249
+ }
250
+ });
251
+ },
252
+
253
+ /* Bind DOM events. */
254
+ bindEvents: function () {
255
+ var my = this;
256
+
257
+ this.element.addEventListener('mouseenter', function (e) {
258
+ my.fireEvent('mouseenter', e);
259
+ my.wavesurfer.fireEvent('region-mouseenter', my, e);
260
+ });
261
+
262
+ this.element.addEventListener('mouseleave', function (e) {
263
+ my.fireEvent('mouseleave', e);
264
+ my.wavesurfer.fireEvent('region-mouseleave', my, e);
265
+ });
266
+
267
+ this.element.addEventListener('click', function (e) {
268
+ e.preventDefault();
269
+ my.fireEvent('click', e);
270
+ my.wavesurfer.fireEvent('region-click', my, e);
271
+ });
272
+
273
+ this.element.addEventListener('dblclick', function (e) {
274
+ e.stopPropagation();
275
+ e.preventDefault();
276
+ my.fireEvent('dblclick', e);
277
+ my.wavesurfer.fireEvent('region-dblclick', my, e);
278
+ });
279
+
280
+ /* Drag or resize on mousemove. */
281
+ (this.drag || this.resize) && (function () {
282
+ var duration = my.wavesurfer.getDuration();
283
+ var drag;
284
+ var resize;
285
+ var startTime;
286
+
287
+ var onDown = function (e) {
288
+ e.stopPropagation();
289
+ startTime = my.wavesurfer.drawer.handleEvent(e) * duration;
290
+
291
+ if (e.target.tagName.toLowerCase() == 'handle') {
292
+ if (e.target.classList.contains('wavesurfer-handle-start')) {
293
+ resize = 'start';
294
+ } else {
295
+ resize = 'end';
296
+ }
297
+ } else {
298
+ drag = true;
299
+ }
300
+ };
301
+ var onUp = function (e) {
302
+ if (drag || resize) {
303
+ drag = false;
304
+ resize = false;
305
+ e.stopPropagation();
306
+ e.preventDefault();
307
+
308
+ my.fireEvent('update-end');
309
+ my.wavesurfer.fireEvent('region-update-end');
310
+ }
311
+ };
312
+ var onMove = function (e) {
313
+ if (drag || resize) {
314
+ var time = my.wavesurfer.drawer.handleEvent(e) * duration;
315
+ var delta = time - startTime;
316
+ startTime = time;
317
+
318
+ // Drag
319
+ if (my.drag && drag) {
320
+ my.onDrag(delta);
321
+ }
322
+
323
+ // Resize
324
+ if (my.resize && resize) {
325
+ my.onResize(delta, resize);
326
+ }
327
+ }
328
+ };
329
+
330
+ my.element.addEventListener('mousedown', onDown);
331
+ my.wrapper.addEventListener('mousemove', onMove);
332
+ document.body.addEventListener('mouseup', onUp);
333
+
334
+ my.on('remove', function () {
335
+ document.body.removeEventListener('mouseup', onUp);
336
+ my.wrapper.removeEventListener('mousemove', onMove);
337
+ });
338
+
339
+ my.wavesurfer.on('destroy', function () {
340
+ document.body.removeEventListener('mouseup', onUp);
341
+ });
342
+ }());
343
+ },
344
+
345
+ onDrag: function (delta) {
346
+ this.update({
347
+ start: this.start + delta,
348
+ end: this.end + delta
349
+ });
350
+ },
351
+
352
+ onResize: function (delta, direction) {
353
+ if (direction == 'start') {
354
+ this.update({
355
+ start: Math.min(this.start + delta, this.end),
356
+ end: Math.max(this.start + delta, this.end)
357
+ });
358
+ } else {
359
+ this.update({
360
+ start: Math.min(this.end + delta, this.start),
361
+ end: Math.max(this.end + delta, this.start)
362
+ });
363
+ }
364
+ }
365
+ };
366
+
367
+ WaveSurfer.util.extend(WaveSurfer.Region, WaveSurfer.Observer);
368
+
369
+
370
+ /* Augment WaveSurfer with region methods. */
371
+ WaveSurfer.initRegions = function () {
372
+ if (!this.regions) {
373
+ this.regions = Object.create(WaveSurfer.Regions);
374
+ this.regions.init(this);
375
+ }
376
+ };
377
+
378
+ WaveSurfer.addRegion = function (options) {
379
+ this.initRegions();
380
+ return this.regions.add(options);
381
+ };
382
+
383
+ WaveSurfer.clearRegions = function () {
384
+ this.regions && this.regions.clear();
385
+ };
386
+
387
+ WaveSurfer.enableDragSelection = function (options) {
388
+ this.initRegions();
389
+ this.regions.enableDragSelection(options);
390
+ };
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ WaveSurfer.Spectrogram = {
4
+ init: function (params) {
5
+ this.params = params;
6
+ var wavesurfer = this.wavesurfer = params.wavesurfer;
7
+
8
+ if (!this.wavesurfer) {
9
+ throw Error('No WaveSurfer intance provided');
10
+ }
11
+
12
+ this.frequenciesDataUrl = params.frequenciesDataUrl;
13
+
14
+ var drawer = this.drawer = this.wavesurfer.drawer;
15
+ this.buffer = this.wavesurfer.backend.buffer;
16
+
17
+ this.container = 'string' == typeof params.container ?
18
+ document.querySelector(params.container) : params.container;
19
+
20
+ if (!this.container) {
21
+ throw Error('No container for WaveSurfer spectrogram');
22
+ }
23
+
24
+ this.width = drawer.width;
25
+ this.pixelRatio = this.params.pixelRatio || wavesurfer.params.pixelRatio;
26
+ this.fftSamples = this.params.fftSamples || wavesurfer.params.fftSamples || 512;
27
+ this.height = this.fftSamples / 2;
28
+
29
+ this.createWrapper();
30
+ this.createCanvas();
31
+ this.render();
32
+
33
+ wavesurfer.drawer.wrapper.onscroll = this.updateScroll.bind(this);
34
+ wavesurfer.on('redraw', this.render.bind(this));
35
+ },
36
+
37
+ createWrapper: function () {
38
+ var wsParams = this.wavesurfer.params;
39
+
40
+ this.wrapper = this.container.appendChild(
41
+ document.createElement('spectrogram')
42
+ );
43
+ this.drawer.style(this.wrapper, {
44
+ display: 'block',
45
+ position: 'relative',
46
+ userSelect: 'none',
47
+ webkitUserSelect: 'none',
48
+ height: this.height + 'px'
49
+ });
50
+
51
+ if (wsParams.fillParent || wsParams.scrollParent) {
52
+ this.drawer.style(this.wrapper, {
53
+ width: '100%',
54
+ overflowX: 'hidden',
55
+ overflowY: 'hidden'
56
+ });
57
+ }
58
+
59
+ var my = this;
60
+ this.wrapper.addEventListener('click', function (e) {
61
+ e.preventDefault();
62
+ var relX = 'offsetX' in e ? e.offsetX : e.layerX;
63
+ my.fireEvent('click', (relX / my.scrollWidth) || 0);
64
+ });
65
+ },
66
+
67
+ createCanvas: function () {
68
+ var canvas = this.canvas = this.wrapper.appendChild(
69
+ document.createElement('canvas')
70
+ );
71
+
72
+ this.spectrCc = canvas.getContext('2d');
73
+
74
+ this.wavesurfer.drawer.style(canvas, {
75
+ position: 'absolute',
76
+ zIndex: 4
77
+ });
78
+ },
79
+
80
+ render: function () {
81
+ this.updateCanvasStyle();
82
+
83
+ if (this.frequenciesDataUrl) {
84
+ this.loadFrequenciesData(this.frequenciesDataUrl);
85
+ }
86
+ else {
87
+ this.getFrequencies(this.drawSpectrogram);
88
+ }
89
+ },
90
+
91
+ updateCanvasStyle: function () {
92
+ var width = Math.round(this.width / this.pixelRatio) + 'px';
93
+ this.canvas.width = this.width;
94
+ this.canvas.height = this.height;
95
+ this.canvas.style.width = width;
96
+ },
97
+
98
+ drawSpectrogram: function(frequenciesData, my) {
99
+ var spectrCc = my.spectrCc;
100
+
101
+ var length = my.wavesurfer.backend.getDuration();
102
+ var height = my.height;
103
+
104
+ var pixels = my.resample(frequenciesData);
105
+
106
+ var heightFactor = 2 / my.buffer.numberOfChannels;
107
+
108
+ for (var i = 0; i < pixels.length; i++) {
109
+ for (var j = 0; j < pixels[i].length; j++) {
110
+ var colorValue = 255 - pixels[i][j];
111
+ my.spectrCc.fillStyle = 'rgb(' + colorValue + ', ' + colorValue + ', ' + colorValue + ')';
112
+ my.spectrCc.fillRect(i, height - j * heightFactor, 1, 1 * heightFactor);
113
+ }
114
+ }
115
+ },
116
+
117
+ getFrequencies: function(callback) {
118
+ var fftSamples = this.fftSamples;
119
+ var buffer = this.buffer;
120
+
121
+ var frequencies = [];
122
+ var context = new window.OfflineAudioContext(1, buffer.length, buffer.sampleRate);
123
+ var source = context.createBufferSource();
124
+ var processor = context.createScriptProcessor(0, 1, 1);
125
+
126
+ var analyser = context.createAnalyser();
127
+ analyser.fftSize = fftSamples;
128
+ analyser.smoothingTimeConstant = (this.width / buffer.duration < 10) ? 0.75 : 0.25;
129
+
130
+ source.buffer = buffer;
131
+
132
+ source.connect(analyser);
133
+ analyser.connect(processor);
134
+ processor.connect(context.destination);
135
+
136
+ processor.onaudioprocess = function () {
137
+ var array = new Uint8Array(analyser.frequencyBinCount);
138
+ analyser.getByteFrequencyData(array);
139
+ frequencies.push(array);
140
+ };
141
+
142
+ source.start(0);
143
+ context.startRendering();
144
+
145
+ var my = this;
146
+ context.oncomplete = function() { callback(frequencies, my); };
147
+ },
148
+
149
+ loadFrequenciesData: function (url) {
150
+ var my = this;
151
+
152
+ var ajax = WaveSurfer.util.ajax({ url: url });
153
+
154
+ ajax.on('success', function(data) { my.drawSpectrogram(JSON.parse(data), my); });
155
+ ajax.on('error', function (e) {
156
+ my.fireEvent('error', 'XHR error: ' + e.target.statusText);
157
+ });
158
+
159
+ return ajax;
160
+ },
161
+
162
+ updateScroll: function(e) {
163
+ this.wrapper.scrollLeft = e.target.scrollLeft;
164
+ },
165
+
166
+ resample: function(oldMatrix, columnsNumber) {
167
+ var columnsNumber = this.width;
168
+ var newMatrix = [];
169
+
170
+ var oldPiece = 1 / oldMatrix.length;
171
+ var newPiece = 1 / columnsNumber;
172
+
173
+ for (var i = 0; i < columnsNumber; i++) {
174
+ var column = new Array(oldMatrix[0].length);
175
+
176
+ for (var j = 0; j < oldMatrix.length; j++) {
177
+ var oldStart = j * oldPiece;
178
+ var oldEnd = oldStart + oldPiece;
179
+ var newStart = i * newPiece;
180
+ var newEnd = newStart + newPiece;
181
+
182
+ var overlap = (oldEnd <= newStart || newEnd <= oldStart) ?
183
+ 0 :
184
+ Math.min(Math.max(oldEnd, newStart), Math.max(newEnd, oldStart)) -
185
+ Math.max(Math.min(oldEnd, newStart), Math.min(newEnd, oldStart));
186
+
187
+ if (overlap > 0) {
188
+ for (var k = 0; k < oldMatrix[0].length; k++) {
189
+ if (column[k] == null) {
190
+ column[k] = 0;
191
+ }
192
+ column[k] += (overlap / newPiece) * oldMatrix[j][k];
193
+ }
194
+ }
195
+ }
196
+
197
+ var intColumn = new Uint8Array(oldMatrix[0].length);
198
+
199
+ for (var k = 0; k < oldMatrix[0].length; k++) {
200
+ intColumn[k] = column[k];
201
+ }
202
+
203
+ newMatrix.push(intColumn);
204
+ }
205
+
206
+ return newMatrix;
207
+ }
208
+ };
209
+
210
+ WaveSurfer.util.extend(WaveSurfer.Spectrogram, WaveSurfer.Observer);