wavesurfer 0.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.
@@ -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
+ };