-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
425 lines (315 loc) · 10.5 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/** @module recordmic */
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
var AudioContext = window.AudioContext || window.webkitAudioContext;
/**
* recordmic can be used to record from the mic in browser via
* getUserMedia. You can pass in some settings when instantiating the recordmic.
*
* The settings object can contain the following properties:
* ```javascript
* {
* volume: 1, // this is the volume at which the mic will record by default
* // this value is 1
* bufferSize: 2048, // this is the size of the buffer as its recording.
* // Default is 2048
* mono: false // whether the mic will record in mono by default this value
* // is false (it will record in stereo) mono can also be 'left'
* // or 'right' to define which channel is being used.
* onSampleData: null // this is a callback if you want to access sampledata
* // as it's being recorded. You can for instance modify
* // data as it's being recorded.
* }
* ```
*
* @class
* @param {Object} settings this is is the settings object. See above for possible values which can be passed in.
* @param {Function} callBack this callback will be called once the mic has "initialized" itself and is ready to record
* @return {recordmic} You can call this as a function or instantiate via new keyword. It will return an instance of recordmic
*/
var recordmic = function( settings, callBack ) {
// handle instancing
if( !( this instanceof recordmic ) )
return new recordmic( settings, callBack );
// setup settings
var s = settings || {};
s.volume = s.volume || 1;
s.bufferSize = s.bufferSize || 2048;
s.mono = s.mono || false;
this.s = s;
// do get usermedia
if( !recordmic.isAvailable ) {
callBack( new Error( 'getUserMedia or audioContext is not available' ) );
} else {
//prompt the user to allow mic
navigator.getUserMedia( { audio: true }, this.onGetUserMedia.bind( this, callBack ), function( e ) {
callBack( e );
});
}
};
/**
* recordmic.isAvailable will be true when recordmic is able to record. In order for recordmic to be able
* to record the browser must have getUserMedia and AudioContext.
*
* @var {Boolean} isAvailable
*/
recordmic.isAvailable = Boolean( navigator.getUserMedia ) && Boolean( AudioContext );
recordmic.prototype = {
/*****************************************/
/*********** GET SET FUNCTIONS ***********/
/*****************************************/
/**
* Call this function to set the volume at which the microphoe will record. Usually this value will be
* between 0 and 1.
*
* @param {Number} volume A value between 0 and 1
*/
setRecordVolume: function( volume ) {
this.s.volume = volume;
this.gain.gain.value = volume;
return this;
},
/**
* Volume at which we're recording between 0 and 1
*
* @return {Number} A volume value between 0 and 1
*/
getRecordVolume: function() {
return this.s.volume;
},
/**
* Will set wether recordmic is recording in mono or not. If you pass in false we'll be recording in
* stereo. If you pass pass in true then the right channel will be used. You can also pass in the strings
* 'left' and 'right' to define which channel is used to record when recording in mono.
*
* @param {String|Boolean} mono false will mean it's recording in stereo. true means that the right channel's data
* will be used. 'left' means the left channel will be used and 'right'
* will mean that the right channel is used.
*/
setMono: function( mono ) {
this.s.mono = mono;
if( mono == 'left' ) {
this.rightData = null;
this.leftData = [];
} else if( mono ) {
this.rightData = [];
this.leftData = null;
} else {
this.leftData = [];
this.rightData = [];
}
return this;
},
/**
* This will return wether we're recording in mono. This value can be either a boolean
* or a string. If true is returned it means we're recording in mono and the right channel
* is used. If false is returned then we're recording in stereo. If a string is returned and it's
* value is 'left' then we're recording in mono using the left channel and 'right' for the
* right channel.
*
* @return {String|Boolean} value for mono either: true, false, 'right', 'left'
*/
getMono: function() {
return this.s.mono;
},
/**
* getChannelData will return return both left and right channel data from our recording.
* If we're recording in mono one of the channels will be null.
*
* The data returned for each channel are Float32Array arrays.
*
* @return {Object} This object will have two variables 'left' and 'right' which
* contain the data for each channel.
*/
getChannelData: function() {
var lCombined, rCombined;
if( this.leftData ) {
lCombined = this.getData( 'left' );
} else {
lCombined = null;
}
if( this.rightData ) {
rCombined = this.getData( 'right' );
}
else {
rCombined = null;
}
return {
left: lCombined,
right: rCombined
};
},
/**
* This will return mono data for our recording. What is returned is a Float32Array.
* The mono setting will determine which array will be returned. If mono is set to true
* then the left channel will be returned over the right.
*
* @param {String} [mono] This is optional. either 'left' or 'right' to determine which channel will be returned.
* @return {Float32Array} The sound data for our recording as mono
*/
getMonoData: function( mono ) {
var combined = null, writePos = 0, data;
combined = new Float32Array( this.recordingLength );
if( mono == 'left' ) {
if( this.leftData ) {
data = this.leftData;
} else {
throw new Error( 'There is nothing recorded for the left channel' );
}
} else if( mono == 'right' ) {
if( this.rightData ) {
data = this.rightData;
} else {
throw new Error( 'There is nothing recorded for the right channel' );
}
} else {
data = this.leftData || this.rightData;
if( !data ) {
throw new Error( 'There is nothing recorded' );
}
}
for( var i = 0, len = data.length; i < len; i++ ) {
for( var j = 0, lenJ = this.s.bufferSize; j < lenJ; j++ ) {
combined[ writePos++ ] = data[ i ][ j ];
}
}
return combined;
},
/**
* getStereoData will return both the left and right channel interleaved as a Float32Array.
*
* You can also pass in a value for mono. If you do then one of the channells will be interleaved as
* stereo data.
*
* So for instance in stereo:
* ```[ left_data1, right_data1, left_data2, right_data2, left_data3, right_data3 ]```
*
* And if mono is set to 'left':
* ```[ left_data1, left_data1, left_data2, left_data2, left_data3, left_data3 ]```
*
* @param {String} mono If you'd like to get mono data interleaved as stereo data either pass 'left' or 'right'
* @return {Float32Array} Sound data interleaved as a Float32Array.
*/
getStereoData: function( mono ) {
var combined = null, writePos = 0, data;
if( !mono && this.leftData && this.rightData ) {
combined = new Float32Array( this.recordingLength * 2 );
for( var i = 0, len = this.leftData.length; i < len; i++ ) {
for( var j = 0, lenJ = this.s.bufferSize; j < lenJ; j++ ) {
combined[ writePos++ ] = this.leftData[ i ][ j ];
combined[ writePos++ ] = this.rightData[ i ][ j ];
}
}
} else {
combined = new Float32Array( this.recordingLength );
if( mono == 'left' ) {
if( this.leftData ) {
data = this.leftData;
} else {
throw new Error( 'There is nothing recorded for the left channel' );
}
} else if( mono == 'right' ) {
if( this.rightData ) {
data = this.rightData;
} else {
throw new Error( 'There is nothing recorded for the right channel' );
}
} else {
data = this.leftData || this.rightData;
if( !data ) {
throw new Error( 'There is nothing recorded' );
}
}
for( var i = 0, len = data.length; i < len; i++ ) {
for( var j = 0, lenJ = this.s.bufferSize; j < lenJ; j++ ) {
combined[ writePos++ ] = data[ i ][ j ];
combined[ writePos++ ] = data[ i ][ j ];
}
}
}
return combined;
},
/*****************************************/
/**************** METHODS ****************/
/*****************************************/
/**
* When you call start you begin recording.
*
* @chainable
*/
start: function() {
this.clear();
window.recordmic_onaudioprocess = this.recorder.onaudioprocess = this.onAudioData.bind( this );
return this;
},
/**
* Call stop to stop recording.
*
* @chainable
*/
stop: function() {
this.recorder.onaudioprocess = undefined;
return this;
},
/**
* This will clear any recorded data. This should be called if you're wanting to record multiple clips.
*
* @chainable
*/
clear: function() {
this.recordingLength = 0;
this.setMono( this.s.mono );
return this;
},
/**
*
* Calling destroy will stop recording and clear all recorded data.
*/
destroy: function() {
this.stop();
this.stream.stop();
this.stream = null;
this.recordingLength = 0;
this.leftData = null;
this.rightData = null;
},
/*****************************************/
/*************** EVENTS ******************/
/*****************************************/
onGetUserMedia: function( callBack, ev ) {
this.stream = ev;
// initialize everything
this.context = new AudioContext();
this.audioInput = this.context.createMediaStreamSource( ev );
this.gain = this.context.createGain();
this.recorder = this.context.createScriptProcessor( this.s.bufferSize, 2, 2);
// now do everything outside of init
this.setRecordVolume( this.s.volume );
this.audioInput.connect( this.gain );
this.gain.connect( this.recorder );
this.recorder.connect( this.context.destination );
this.setMono( this.s.mono );
callBack( undefined, this );
},
onAudioData: function( ev ) {
var left, right, leftData, rightData;
left = ev.inputBuffer.getChannelData( 0 );
right = ev.inputBuffer.getChannelData( 1 );
// do the call back and send the current data
// this allows users for instance to modify data
// on the fly also
if( this.s.onSampleData ) {
this.s.onSampleData( left, right );
}
// now do recording
if( this.leftData ) {
leftData = new Float32Array( left );
this.leftData.push( leftData );
}
if( this.rightData ) {
rightData = new Float32Array( right );
this.rightData.push( rightData );
}
this.recordingLength += this.s.bufferSize;
}
};
module.exports = recordmic;