wavesurfer-rails 0.1.12 → 0.1.21
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 +4 -4
- data/README.md +12 -1
- data/lib/wavesurfer/rails/version.rb +1 -1
- data/vendor/assets/javascripts/wavesurfer-plugins.js +6 -6
- data/vendor/assets/javascripts/ws-plugins/wavesurfer.elan.js +252 -0
- data/vendor/assets/javascripts/ws-plugins/wavesurfer.microphone.js +173 -0
- data/vendor/assets/javascripts/ws-plugins/wavesurfer.minimap.js +213 -0
- data/vendor/assets/javascripts/ws-plugins/wavesurfer.regions.js +416 -0
- data/vendor/assets/javascripts/ws-plugins/wavesurfer.spectrogram.js +223 -0
- data/vendor/assets/javascripts/ws-plugins/wavesurfer.timeline.js +196 -0
- metadata +8 -8
- data/vendor/assets/javascripts/plugin/wavesurfer.elan.min.js +0 -3
- data/vendor/assets/javascripts/plugin/wavesurfer.microphone.min.js +0 -3
- data/vendor/assets/javascripts/plugin/wavesurfer.minimap.min.js +0 -3
- data/vendor/assets/javascripts/plugin/wavesurfer.regions.min.js +0 -3
- data/vendor/assets/javascripts/plugin/wavesurfer.spectrogram.min.js +0 -3
- data/vendor/assets/javascripts/plugin/wavesurfer.timeline.min.js +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a067765fcb89f4a31cc56ad2c9826b59c4f36c22
|
4
|
+
data.tar.gz: 2f116d093e7d910c1b923491fe71ee3d634c2746
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f7b9f368a776ab4385a891530a5e4be2599b33b5d8c57b4aa9ef84bd60b3178d6dfce724a203f97b6034d768a15dd55e6594df57c4aa027a3c5c6122c4d95d4
|
7
|
+
data.tar.gz: 144a1f0c58341bc1e3fb9bcac2fee0cb2c8c6d607b65f3aab140fdd1a7794bde6a6249338a5cd28be679c0b39a58f99c80a18775d4189284d872e0d589337d15
|
data/README.md
CHANGED
@@ -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
|
27
|
+
//= require ws-plugins/wavesurfer.microphone
|
28
|
+
//= require ws-plugins/wavesurfer.minimap
|
29
|
+
//= require ws-plugins/wavesurfer.regions
|
30
|
+
//= require ws-plugins/wavesurfer.spectrogram
|
31
|
+
//= require ws-plugins/wavesurfer.timeline
|
32
|
+
```
|
33
|
+
|
34
|
+
Or require them all at once:
|
24
35
|
|
25
36
|
```javascript
|
26
37
|
//= require wavesurfer-plugins
|
@@ -1,6 +1,6 @@
|
|
1
|
-
//= require
|
2
|
-
//= require
|
3
|
-
//= require
|
4
|
-
//= require
|
5
|
-
//= require
|
6
|
-
//= require
|
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
|
+
}));
|