wavesurfer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 681097b7ed76d7f145a5057975cbb28290a6c514
4
+ data.tar.gz: 6eae3e0b07cd65f61b0cb819b745e9a11ce25133
5
+ SHA512:
6
+ metadata.gz: 076cf51b74820f0880cc2b1338a42b424ccbf4d874d59b8cac15ac465744e44f2b8e108cab494a51fbe361817333920cea7e27b94d04c1fa7b3fe38d359ba541
7
+ data.tar.gz: 9dbb1ad884e6e81a92d5369ea3a1b0c4a6a862b7180608fe01531c1bef1f1cebeabaee6f2ea4c5a17e7b0a61be791e810af7f24d5fd893f2206055d4ab276a43
@@ -0,0 +1,6 @@
1
+ require 'wavesurfer/version'
2
+
3
+ module Wavesurfer
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Wavesurfer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ WaveSurfer.ELAN = {
4
+ Types: {
5
+ ALIGNABLE_ANNOTATION: 'ALIGNABLE_ANNOTATION',
6
+ REF_ANNOTATION: 'REF_ANNOTATION'
7
+ },
8
+
9
+ init: function (params) {
10
+ this.data = null;
11
+ this.params = params;
12
+ this.container = 'string' == typeof params.container ?
13
+ document.querySelector(params.container) : params.container;
14
+
15
+ if (!this.container) {
16
+ throw Error('No container for ELAN');
17
+ }
18
+
19
+ this.bindClick();
20
+
21
+ if (params.url) {
22
+ this.load(params.url);
23
+ }
24
+ },
25
+
26
+ load: function (url) {
27
+ var my = this;
28
+ this.loadXML(url, function (xml) {
29
+ my.data = my.parseElan(xml);
30
+ my.render();
31
+ my.fireEvent('ready', my.data);
32
+ });
33
+ },
34
+
35
+ loadXML: function (url, callback) {
36
+ var xhr = new XMLHttpRequest();
37
+ xhr.open('GET', url, true);
38
+ xhr.responseType = 'document';
39
+ xhr.send();
40
+ xhr.addEventListener('load', function (e) {
41
+ callback && callback(e.target.responseXML);
42
+ });
43
+ },
44
+
45
+ parseElan: function (xml) {
46
+ var _forEach = Array.prototype.forEach;
47
+ var _map = Array.prototype.map;
48
+
49
+ var data = {
50
+ media: {},
51
+ timeOrder: {},
52
+ tiers: [],
53
+ annotations: {},
54
+ alignableAnnotations: []
55
+ };
56
+
57
+ var header = xml.querySelector('HEADER');
58
+ var inMilliseconds = header.getAttribute('TIME_UNITS') == 'milliseconds';
59
+ var media = header.querySelector('MEDIA_DESCRIPTOR');
60
+ data.media.url = media.getAttribute('MEDIA_URL');
61
+ data.media.type = media.getAttribute('MIME_TYPE');
62
+
63
+ var timeSlots = xml.querySelectorAll('TIME_ORDER TIME_SLOT');
64
+ var timeOrder = {};
65
+ _forEach.call(timeSlots, function (slot) {
66
+ var value = parseFloat(slot.getAttribute('TIME_VALUE'));
67
+ // If in milliseconds, convert to seconds with rounding
68
+ if (inMilliseconds) {
69
+ value = Math.round(value * 1e2) / 1e5;
70
+ }
71
+ timeOrder[slot.getAttribute('TIME_SLOT_ID')] = value;
72
+ });
73
+
74
+ data.tiers = _map.call(xml.querySelectorAll('TIER'), function (tier) {
75
+ return {
76
+ id: tier.getAttribute('TIER_ID'),
77
+ linguisticTypeRef: tier.getAttribute('LINGUISTIC_TYPE_REF'),
78
+ defaultLocale: tier.getAttribute('DEFAULT_LOCALE'),
79
+ annotations: _map.call(
80
+ tier.querySelectorAll('REF_ANNOTATION, ALIGNABLE_ANNOTATION'),
81
+ function (node) {
82
+ var annot = {
83
+ type: node.nodeName,
84
+ id: node.getAttribute('ANNOTATION_ID'),
85
+ ref: node.getAttribute('ANNOTATION_REF'),
86
+ value: node.querySelector('ANNOTATION_VALUE')
87
+ .textContent.trim()
88
+ };
89
+
90
+ if (this.Types.ALIGNABLE_ANNOTATION == annot.type) {
91
+ // Add start & end to alignable annotation
92
+ annot.start = timeOrder[node.getAttribute('TIME_SLOT_REF1')];
93
+ annot.end = timeOrder[node.getAttribute('TIME_SLOT_REF2')];
94
+
95
+ // Add to the list of alignable annotations
96
+ data.alignableAnnotations.push(annot);
97
+ }
98
+
99
+ // Additionally, put into the flat map of all annotations
100
+ data.annotations[annot.id] = annot;
101
+
102
+ return annot;
103
+ }, this
104
+ )
105
+ };
106
+ }, this);
107
+
108
+ // Create JavaScript references between annotations
109
+ data.tiers.forEach(function (tier) {
110
+ tier.annotations.forEach(function (annot) {
111
+ if (null != annot.ref) {
112
+ annot.reference = data.annotations[annot.ref];
113
+ }
114
+ }, this);
115
+ }, this);
116
+
117
+ // Sort alignable annotations by start & end
118
+ data.alignableAnnotations.sort(function (a, b) {
119
+ var d = a.start - b.start;
120
+ if (d == 0) {
121
+ d = b.end - a.end;
122
+ }
123
+ return d;
124
+ });
125
+
126
+ data.length = data.alignableAnnotations.length;
127
+
128
+ return data;
129
+ },
130
+
131
+ render: function () {
132
+ // apply tiers filter
133
+ var tiers = this.data.tiers;
134
+ if (this.params.tiers) {
135
+ tiers = tiers.filter(function (tier) {
136
+ return tier.id in this.params.tiers;
137
+ }, this);
138
+ }
139
+
140
+ // denormalize references to alignable annotations
141
+ var backRefs = {};
142
+ var indeces = {};
143
+ tiers.forEach(function (tier, index) {
144
+ tier.annotations.forEach(function (annot) {
145
+ if (annot.reference &&
146
+ annot.reference.type == this.Types.ALIGNABLE_ANNOTATION) {
147
+ if (!(annot.reference.id in backRefs)) {
148
+ backRefs[annot.ref] = {};
149
+ }
150
+ backRefs[annot.ref][index] = annot;
151
+ indeces[index] = true;
152
+ }
153
+ }, this);
154
+ }, this);
155
+ indeces = Object.keys(indeces).sort();
156
+
157
+ this.renderedAlignable = this.data.alignableAnnotations.filter(
158
+ function (alignable) {
159
+ return backRefs[alignable.id];
160
+ }
161
+ );
162
+
163
+ // table
164
+ var table = document.createElement('table');
165
+ table.className = 'wavesurfer-annotations';
166
+
167
+ // head
168
+ var thead = document.createElement('thead');
169
+ var headRow = document.createElement('tr');
170
+ thead.appendChild(headRow);
171
+ table.appendChild(thead);
172
+ var th = document.createElement('th');
173
+ th.textContent = 'Time';
174
+ th.className = 'wavesurfer-time';
175
+ headRow.appendChild(th);
176
+ indeces.forEach(function (index) {
177
+ var tier = tiers[index];
178
+ var th = document.createElement('th');
179
+ th.className = 'wavesurfer-tier-' + tier.id;
180
+ th.textContent = tier.id;
181
+ th.style.width = this.params.tiers[tier.id];
182
+ headRow.appendChild(th);
183
+ }, this);
184
+
185
+ // body
186
+ var tbody = document.createElement('tbody');
187
+ table.appendChild(tbody);
188
+ this.renderedAlignable.forEach(function (alignable) {
189
+ var row = document.createElement('tr');
190
+ row.id = 'wavesurfer-alignable-' + alignable.id;
191
+ tbody.appendChild(row);
192
+
193
+ var td = document.createElement('td');
194
+ td.className = 'wavesurfer-time';
195
+ td.textContent = alignable.start.toFixed(1) + '–' +
196
+ alignable.end.toFixed(1);
197
+ row.appendChild(td);
198
+
199
+ var backRef = backRefs[alignable.id];
200
+ indeces.forEach(function (index) {
201
+ var tier = tiers[index];
202
+ var td = document.createElement('td');
203
+ var annotation = backRef[index];
204
+ if (annotation) {
205
+ td.id = 'wavesurfer-annotation-' + annotation.id;
206
+ td.dataset.ref = alignable.id;
207
+ td.dataset.start = alignable.start;
208
+ td.dataset.end = alignable.end;
209
+ td.textContent = annotation.value;
210
+ }
211
+ td.className = 'wavesurfer-tier-' + tier.id;
212
+ row.appendChild(td);
213
+ }, this);
214
+ }, this);
215
+
216
+ this.container.innerHTML = '';
217
+ this.container.appendChild(table);
218
+ },
219
+
220
+ bindClick: function () {
221
+ var my = this;
222
+ this.container.addEventListener('click', function (e) {
223
+ var ref = e.target.dataset.ref;
224
+ if (null != ref) {
225
+ var annot = my.data.annotations[ref];
226
+ if (annot) {
227
+ my.fireEvent('select', annot.start, annot.end);
228
+ }
229
+ }
230
+ });
231
+ },
232
+
233
+ getRenderedAnnotation: function (time) {
234
+ var result;
235
+ this.renderedAlignable.some(function (annotation) {
236
+ if (annotation.start <= time && annotation.end >= time) {
237
+ result = annotation;
238
+ return true;
239
+ }
240
+ return false;
241
+ });
242
+ return result;
243
+ },
244
+
245
+ getAnnotationNode: function (annotation) {
246
+ return document.getElementById(
247
+ 'wavesurfer-alignable-' + annotation.id
248
+ );
249
+ }
250
+ };
251
+
252
+ WaveSurfer.util.extend(WaveSurfer.ELAN, WaveSurfer.Observer);
@@ -0,0 +1,173 @@
1
+ (function (root, factory) {
2
+ if (typeof define === 'function' && define.amd) {
3
+ define(['wavesurfer'], factory);
4
+ } else {
5
+ root.WaveSurfer.Microphone = factory(root.WaveSurfer);
6
+ }
7
+ }(this, function (WaveSurfer) {
8
+ 'use strict';
9
+
10
+ WaveSurfer.Microphone = {
11
+ init: function (params) {
12
+ this.params = params;
13
+
14
+ var wavesurfer = this.wavesurfer = params.wavesurfer;
15
+
16
+ if (!this.wavesurfer) {
17
+ throw new Error('No WaveSurfer instance provided');
18
+ }
19
+
20
+ this.active = false;
21
+ this.paused = false;
22
+
23
+ // cross-browser getUserMedia
24
+ this.getUserMedia = (
25
+ navigator.getUserMedia ||
26
+ navigator.webkitGetUserMedia ||
27
+ navigator.mozGetUserMedia ||
28
+ navigator.msGetUserMedia
29
+ ).bind(navigator);
30
+
31
+ // The buffer size in units of sample-frames.
32
+ // If specified, the bufferSize must be one of the following values:
33
+ // 256, 512, 1024, 2048, 4096, 8192, 16384. Defaults to 4096.
34
+ this.bufferSize = this.params.bufferSize || 4096;
35
+
36
+ // Integer specifying the number of channels for this node's input,
37
+ // defaults to 1. Values of up to 32 are supported.
38
+ this.numberOfInputChannels = this.params.numberOfInputChannels || 1;
39
+
40
+ // Integer specifying the number of channels for this node's output,
41
+ // defaults to 1. Values of up to 32 are supported.
42
+ this.numberOfOutputChannels = this.params.numberOfOutputChannels || 1;
43
+
44
+ // wavesurfer's AudioContext where we'll route the mic signal to
45
+ this.micContext = this.wavesurfer.backend.getAudioContext();
46
+ },
47
+
48
+ /**
49
+ * Allow user to select audio input device, eg. microphone, and
50
+ * start the visualization.
51
+ */
52
+ start: function() {
53
+ this.getUserMedia({
54
+ video: false,
55
+ audio: true
56
+ },
57
+ this.gotStream.bind(this),
58
+ this.deviceError.bind(this));
59
+ },
60
+
61
+ /**
62
+ * Pause/resume visualization.
63
+ */
64
+ togglePlay: function() {
65
+ if (!this.active) {
66
+ // start it first
67
+ this.start();
68
+ } else {
69
+ // toggle paused
70
+ this.paused = !this.paused;
71
+
72
+ if (this.paused) {
73
+ // disconnect sources so they can be used elsewhere
74
+ // (eg. during audio playback)
75
+ this.disconnect();
76
+ } else {
77
+ // resume visualization
78
+ this.connect();
79
+ }
80
+ }
81
+ },
82
+
83
+ /**
84
+ * Stop the microphone and visualization.
85
+ */
86
+ stop: function() {
87
+ if (this.active) {
88
+ this.active = false;
89
+
90
+ if (this.stream) {
91
+ this.stream.stop();
92
+ }
93
+ this.disconnect();
94
+ this.wavesurfer.empty();
95
+ }
96
+ },
97
+
98
+ /**
99
+ * Connect the media sources that feed the visualization.
100
+ */
101
+ connect: function() {
102
+ if (this.stream !== undefined) {
103
+ // Create an AudioNode from the stream.
104
+ this.mediaStreamSource = this.micContext.createMediaStreamSource(this.stream);
105
+
106
+ this.levelChecker = this.micContext.createScriptProcessor(
107
+ this.bufferSize, this.numberOfInputChannels, this.numberOfOutputChannels);
108
+ this.mediaStreamSource.connect(this.levelChecker);
109
+
110
+ this.levelChecker.connect(this.micContext.destination);
111
+ this.levelChecker.onaudioprocess = this.reloadBuffer.bind(this);
112
+ }
113
+ },
114
+
115
+ /**
116
+ * Disconnect the media sources that feed the visualization.
117
+ */
118
+ disconnect: function() {
119
+ if (this.mediaStreamSource !== undefined) {
120
+ this.mediaStreamSource.disconnect();
121
+ }
122
+
123
+ if (this.levelChecker !== undefined) {
124
+ this.levelChecker.disconnect();
125
+ }
126
+ },
127
+
128
+ /**
129
+ * Redraw the waveform.
130
+ */
131
+ reloadBuffer: function(event) {
132
+ if (!this.paused) {
133
+ this.wavesurfer.empty();
134
+ this.wavesurfer.loadDecodedBuffer(event.inputBuffer);
135
+ }
136
+ },
137
+
138
+ /**
139
+ * Audio input device is ready.
140
+ *
141
+ * @param {LocalMediaStream} stream: the microphone's media stream.
142
+ */
143
+ gotStream: function(stream) {
144
+ this.stream = stream;
145
+ this.active = true;
146
+
147
+ this.connect();
148
+
149
+ // notify listeners
150
+ this.fireEvent('deviceReady', stream);
151
+ },
152
+
153
+ /**
154
+ * Destroy the microphone plugin.
155
+ */
156
+ destroy: function(event) {
157
+ this.stop();
158
+ },
159
+
160
+ /**
161
+ * Device error callback.
162
+ */
163
+ deviceError: function(code) {
164
+ // notify listeners
165
+ this.fireEvent('deviceError', code);
166
+ }
167
+
168
+ };
169
+
170
+ WaveSurfer.util.extend(WaveSurfer.Microphone, WaveSurfer.Observer);
171
+
172
+ return WaveSurfer.Microphone;
173
+ }));
@@ -0,0 +1,200 @@
1
+ 'use strict';
2
+
3
+ /* Minimap */
4
+ WaveSurfer.Minimap = WaveSurfer.util.extend({}, WaveSurfer.Drawer, WaveSurfer.Drawer.Canvas, {
5
+ init: function (wavesurfer, params) {
6
+ this.wavesurfer = wavesurfer;
7
+ this.container = this.wavesurfer.drawer.container;
8
+ this.lastPos = this.wavesurfer.drawer.lastPos;
9
+ this.params = wavesurfer.util.extend(
10
+ {}, this.wavesurfer.drawer.params, {
11
+ showRegions: false,
12
+ showOverview: false,
13
+ overviewBorderColor: 'green',
14
+ overviewBorderSize: 2
15
+ }, params, {
16
+ scrollParent: false,
17
+ fillParent: true
18
+ }
19
+ );
20
+
21
+ this.width = 0;
22
+ this.height = this.params.height * this.params.pixelRatio;
23
+
24
+ this.createWrapper();
25
+ this.createElements();
26
+
27
+ if (WaveSurfer.Regions && this.params.showRegions) {
28
+ this.regions();
29
+ }
30
+
31
+ this.bindWaveSurferEvents();
32
+ this.bindMinimapEvents();
33
+ },
34
+ regions: function() {
35
+ var my = this;
36
+ this.regions = {};
37
+
38
+ this.wavesurfer.on('region-created', function(region) {
39
+ my.regions[region.id] = region;
40
+ my.renderRegions();
41
+ });
42
+
43
+ this.wavesurfer.on('region-updated', function(region) {
44
+ my.regions[region.id] = region;
45
+ my.renderRegions();
46
+ });
47
+
48
+ this.wavesurfer.on('region-removed', function(region) {
49
+ delete my.regions[region.id];
50
+ my.renderRegions();
51
+ });
52
+ },
53
+ renderRegions: function() {
54
+ var my = this;
55
+ var regionElements = this.wrapper.querySelectorAll('region');
56
+ for (var i = 0; i < regionElements.length; ++i) {
57
+ this.wrapper.removeChild(regionElements[i]);
58
+ }
59
+
60
+ Object.keys(this.regions).forEach(function(id){
61
+ var region = my.regions[id];
62
+ var width = (my.width * ((region.end - region.start) / my.wavesurfer.getDuration()));
63
+ var left = (my.width * (region.start / my.wavesurfer.getDuration()));
64
+ var regionElement = my.style(document.createElement('region'), {
65
+ height: 'inherit',
66
+ backgroundColor: region.color,
67
+ width: width + 'px',
68
+ left: left + 'px',
69
+ display: 'block',
70
+ position: 'absolute'
71
+ });
72
+ regionElement.classList.add(id);
73
+ my.wrapper.appendChild(regionElement);
74
+ });
75
+ },
76
+ createElements: function() {
77
+ WaveSurfer.Drawer.Canvas.createElements.call(this);
78
+
79
+ if (this.params.showOverview) {
80
+ this.overviewRegion = this.style(document.createElement('overview'), {
81
+ height: (this.wrapper.offsetHeight - (this.params.overviewBorderSize * 2)) + 'px',
82
+ width: '0px',
83
+ display: 'block',
84
+ position: 'absolute',
85
+ cursor: 'move',
86
+ border: this.params.overviewBorderSize + 'px solid ' + this.params.overviewBorderColor,
87
+ zIndex: 2,
88
+ opacity: this.params.overviewOpacity
89
+ });
90
+
91
+ this.wrapper.appendChild(this.overviewRegion);
92
+ }
93
+ },
94
+
95
+ bindWaveSurferEvents: function () {
96
+ var my = this;
97
+ this.wavesurfer.on('ready', this.render.bind(this));
98
+ this.wavesurfer.on('audioprocess', function (currentTime) {
99
+ my.progress(my.wavesurfer.backend.getPlayedPercents());
100
+ });
101
+ this.wavesurfer.on('seek', function(progress) {
102
+ my.progress(my.wavesurfer.backend.getPlayedPercents());
103
+ });
104
+
105
+ if (this.params.showOverview) {
106
+ this.wavesurfer.on('scroll', function(event) {
107
+ if (!my.draggingOverview) {
108
+ my.moveOverviewRegion(event.target.scrollLeft / my.ratio);
109
+ }
110
+ });
111
+
112
+ this.wavesurfer.drawer.wrapper.addEventListener('mouseover', function(event) {
113
+ if (my.draggingOverview) {
114
+ my.draggingOverview = false;
115
+ }
116
+ });
117
+ }
118
+
119
+ this.wavesurfer.on('destroy', this.destroy.bind(this));
120
+ },
121
+
122
+ bindMinimapEvents: function () {
123
+ var my = this;
124
+ var relativePositionX = 0;
125
+ var seek = true;
126
+ var positionMouseDown = {
127
+ clientX: 0,
128
+ clientY: 0
129
+ };
130
+
131
+ this.on('click', (function (e, position) {
132
+ if (seek) {
133
+ this.progress(position);
134
+ this.wavesurfer.seekAndCenter(position);
135
+ } else {
136
+ seek = true;
137
+ }
138
+ }).bind(this));
139
+
140
+ if (this.params.showOverview) {
141
+ this.overviewRegion.addEventListener('mousedown', function(event) {
142
+ my.draggingOverview = true;
143
+ relativePositionX = event.layerX;
144
+ positionMouseDown.clientX = event.clientX;
145
+ positionMouseDown.clientY = event.clientY;
146
+ });
147
+
148
+ this.wrapper.addEventListener('mousemove', function(event) {
149
+ if(my.draggingOverview) {
150
+ my.moveOverviewRegion(event.clientX - my.container.getBoundingClientRect().left - relativePositionX);
151
+ }
152
+ });
153
+
154
+ this.wrapper.addEventListener('mouseup', function(event) {
155
+ if (positionMouseDown.clientX - event.clientX === 0 && positionMouseDown.clientX - event.clientX === 0) {
156
+ seek = true;
157
+ my.draggingOverview = false;
158
+ } else if (my.draggingOverview) {
159
+ seek = false;
160
+ my.draggingOverview = false;
161
+ }
162
+ });
163
+ }
164
+ },
165
+
166
+ render: function () {
167
+ var len = this.getWidth();
168
+ var peaks = this.wavesurfer.backend.getPeaks(len);
169
+ this.drawPeaks(peaks, len);
170
+
171
+ if (this.params.showOverview) {
172
+ //get proportional width of overview region considering the respective
173
+ //width of the drawers
174
+ this.ratio = this.wavesurfer.drawer.width / this.width;
175
+ this.waveShowedWidth = this.wavesurfer.drawer.width / this.ratio;
176
+ this.waveWidth = this.wavesurfer.drawer.width;
177
+ this.overviewWidth = (this.width / this.ratio);
178
+ this.overviewPosition = 0;
179
+ this.overviewRegion.style.width = (this.overviewWidth - (this.params.overviewBorderSize * 2)) + 'px';
180
+ }
181
+ },
182
+ moveOverviewRegion: function(pixels) {
183
+ if (pixels < 0) {
184
+ this.overviewPosition = 0;
185
+ } else if (pixels + this.overviewWidth < this.width) {
186
+ this.overviewPosition = pixels;
187
+ } else {
188
+ this.overviewPosition = (this.width - this.overviewWidth);
189
+ }
190
+ this.overviewRegion.style.left = this.overviewPosition + 'px';
191
+ this.wavesurfer.drawer.wrapper.scrollLeft = this.overviewPosition * this.ratio;
192
+ }
193
+ });
194
+
195
+
196
+ WaveSurfer.initMinimap = function (params) {
197
+ var map = Object.create(WaveSurfer.Minimap);
198
+ map.init(this, params);
199
+ return map;
200
+ };