wavesurfer-rails 0.1.1 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d583a26ff8fe19758090aa446c2888af1f3578c6
4
- data.tar.gz: 01700088d6050b7b858d6cfbf9bce1098c9dc5cf
3
+ metadata.gz: 2ba42fdbe55d716deca91292edf9857e6771bb8d
4
+ data.tar.gz: 88fd5152b12e3f2d7a9a77f09c8f9b1781f23911
5
5
  SHA512:
6
- metadata.gz: 49608f4c19dd4cbbe3efabaa5de60f4d02142fe436206a0a1a62150e73d1840fa2f5bb9306bd8b0c5d81ea6b03ffaba60a4db79e07549e7ba2028945412339a0
7
- data.tar.gz: f32462fd869f0a0d02eb2d326cf1b204b5f3a4fa1637964fc58cd437dc6296622f6c19630449ffec1cb93692e2042aa362f9cdbc4386e13edf4103580342a0f9
6
+ metadata.gz: cc7175991a6695b50c5e011982197f31e4def0b297d09005dc32bc9645d83347cd96219553702ce1be8e717ee3b5e3265c5171a0c9f02b3524386f3e25c06717
7
+ data.tar.gz: cf77560f0044a0ca8b092b7f05c62142ab8880ed785dbac4e6c054dfe21709cd7d349870b1fdb16f18ba5920298ec5dc59acab894f7ef01c5d1a726d77592d68
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Wavesurfer-rails
2
2
 
3
- [Wavesurfer-js](http://wavesurfer-js.org/)
3
+ [Wavesurfer Homepage](http://wavesurfer-js.org/)
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,7 +20,18 @@ In `app/assets/javascripts/application.js` put the following:
20
20
  //= require wavesurfer
21
21
  ```
22
22
 
23
- If you want the wavesurfer plugins:
23
+ If you want the wavesurfer plugins, you can require them all individually:
24
+
25
+ ```javascript
26
+ //= require ws-plugins/wavesurfer.elan.js
27
+ //= require ws-plugins/wavesurfer.microphone.js
28
+ //= require ws-plugins/wavesurfer.minimap.js
29
+ //= require ws-plugins/wavesurfer.regions.js
30
+ //= require ws-plugins/wavesurfer.spectrogram.js
31
+ //= require ws-plugins/wavesurfer.timeline.js
32
+ ```
33
+
34
+ Or require them all at once:
24
35
 
25
36
  ```javascript
26
37
  //= require wavesurfer-plugins
@@ -1,5 +1,5 @@
1
1
  module Wavesurfer
2
2
  module Rails
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
- //= require plugin/wavesurfer.elan.min.js
2
- //= require plugin/wavesurfer.microphone.min.js
3
- //= require plugin/wavesurfer.minimap.min.js
4
- //= require plugin/wavesurfer.regions.min.js
5
- //= require plugin/wavesurfer.spectrogram.min.js
6
- //= require plugin/wavesurfer.timeline.min.js
1
+ //= require ws-plugins/wavesurfer.elan.js
2
+ //= require ws-plugins/wavesurfer.microphone.js
3
+ //= require ws-plugins/wavesurfer.minimap.js
4
+ //= require ws-plugins/wavesurfer.regions.js
5
+ //= require ws-plugins/wavesurfer.spectrogram.js
6
+ //= require ws-plugins/wavesurfer.timeline.js
@@ -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
+ }));