1 | var Sink = this.Sink = function (global) {
|
---|
2 |
|
---|
3 | /**
|
---|
4 | * Creates a Sink according to specified parameters, if possible.
|
---|
5 | *
|
---|
6 | * @class
|
---|
7 | *
|
---|
8 | * @arg =!readFn
|
---|
9 | * @arg =!channelCount
|
---|
10 | * @arg =!bufferSize
|
---|
11 | * @arg =!sampleRate
|
---|
12 | *
|
---|
13 | * @param {Function} readFn A callback to handle the buffer fills.
|
---|
14 | * @param {Number} channelCount Channel count.
|
---|
15 | * @param {Number} bufferSize (Optional) Specifies a pre-buffer size to control the amount of latency.
|
---|
16 | * @param {Number} sampleRate Sample rate (ms).
|
---|
17 | * @param {Number} default=0 writePosition Write position of the sink, as in how many samples have been written per channel.
|
---|
18 | * @param {String} default=async writeMode The default mode of writing to the sink.
|
---|
19 | * @param {String} default=interleaved channelMode The mode in which the sink asks the sample buffers to be channeled in.
|
---|
20 | * @param {Number} default=0 previousHit The previous time of a callback.
|
---|
21 | * @param {Buffer} default=null ringBuffer The ring buffer array of the sink. If null, ring buffering will not be applied.
|
---|
22 | * @param {Number} default=0 ringOffset The current position of the ring buffer.
|
---|
23 | */
|
---|
24 | function Sink (readFn, channelCount, bufferSize, sampleRate) {
|
---|
25 | var sinks = Sink.sinks.list,
|
---|
26 | i;
|
---|
27 | for (i=0; i<sinks.length; i++) {
|
---|
28 | if (sinks[i].enabled) {
|
---|
29 | try {
|
---|
30 | return new sinks[i](readFn, channelCount, bufferSize, sampleRate);
|
---|
31 | } catch(e1){}
|
---|
32 | }
|
---|
33 | }
|
---|
34 |
|
---|
35 | throw Sink.Error(0x02);
|
---|
36 | }
|
---|
37 |
|
---|
38 | function SinkClass () {
|
---|
39 | }
|
---|
40 |
|
---|
41 | Sink.SinkClass = SinkClass;
|
---|
42 |
|
---|
43 | SinkClass.prototype = Sink.prototype = {
|
---|
44 | sampleRate: 44100,
|
---|
45 | channelCount: 2,
|
---|
46 | bufferSize: 4096,
|
---|
47 |
|
---|
48 | writePosition: 0,
|
---|
49 | previousHit: 0,
|
---|
50 | ringOffset: 0,
|
---|
51 |
|
---|
52 | channelMode: 'interleaved',
|
---|
53 | isReady: false,
|
---|
54 |
|
---|
55 | /**
|
---|
56 | * Does the initialization of the sink.
|
---|
57 | * @method Sink
|
---|
58 | */
|
---|
59 | start: function (readFn, channelCount, bufferSize, sampleRate) {
|
---|
60 | this.channelCount = isNaN(channelCount) || channelCount === null ? this.channelCount: channelCount;
|
---|
61 | this.bufferSize = isNaN(bufferSize) || bufferSize === null ? this.bufferSize : bufferSize;
|
---|
62 | this.sampleRate = isNaN(sampleRate) || sampleRate === null ? this.sampleRate : sampleRate;
|
---|
63 | this.readFn = readFn;
|
---|
64 | this.activeRecordings = [];
|
---|
65 | this.previousHit = +new Date();
|
---|
66 | Sink.EventEmitter.call(this);
|
---|
67 | Sink.emit('init', [this].concat([].slice.call(arguments)));
|
---|
68 | },
|
---|
69 | /**
|
---|
70 | * The method which will handle all the different types of processing applied on a callback.
|
---|
71 | * @method Sink
|
---|
72 | */
|
---|
73 | process: function (soundData, channelCount) {
|
---|
74 | this.emit('preprocess', arguments);
|
---|
75 |
|
---|
76 | if (this.ringBuffer) {
|
---|
77 | (this.channelMode === 'interleaved' ? this.ringSpin : this.ringSpinInterleaved).apply(this, arguments);
|
---|
78 | }
|
---|
79 |
|
---|
80 | if (this.channelMode === 'interleaved') {
|
---|
81 | this.emit('audioprocess', arguments);
|
---|
82 |
|
---|
83 | if (this.readFn) {
|
---|
84 | this.readFn.apply(this, arguments);
|
---|
85 | }
|
---|
86 | } else {
|
---|
87 | var soundDataSplit = Sink.deinterleave(soundData, this.channelCount),
|
---|
88 | args = [soundDataSplit].concat([].slice.call(arguments, 1));
|
---|
89 | this.emit('audioprocess', args);
|
---|
90 |
|
---|
91 | if (this.readFn) {
|
---|
92 | this.readFn.apply(this, args);
|
---|
93 | }
|
---|
94 |
|
---|
95 | Sink.interleave(soundDataSplit, this.channelCount, soundData);
|
---|
96 | }
|
---|
97 | this.emit('postprocess', arguments);
|
---|
98 | this.previousHit = +new Date();
|
---|
99 | this.writePosition += soundData.length / channelCount;
|
---|
100 | },
|
---|
101 | /**
|
---|
102 | * Get the current output position, defaults to writePosition - bufferSize.
|
---|
103 | *
|
---|
104 | * @method Sink
|
---|
105 | *
|
---|
106 | * @return {Number} The position of the write head, in samples, per channel.
|
---|
107 | */
|
---|
108 | getPlaybackTime: function () {
|
---|
109 | return this.writePosition - this.bufferSize;
|
---|
110 | },
|
---|
111 | /**
|
---|
112 | * Internal method to send the ready signal if not ready yet.
|
---|
113 | * @method Sink
|
---|
114 | */
|
---|
115 | ready: function () {
|
---|
116 | if (this.isReady) return;
|
---|
117 |
|
---|
118 | this.isReady = true;
|
---|
119 | this.emit('ready', []);
|
---|
120 | }
|
---|
121 | };
|
---|
122 |
|
---|
123 | /**
|
---|
124 | * The container for all the available sinks. Also a decorator function for creating a new Sink class and binding it.
|
---|
125 | *
|
---|
126 | * @method Sink
|
---|
127 | * @static
|
---|
128 | *
|
---|
129 | * @arg {String} type The name / type of the Sink.
|
---|
130 | * @arg {Function} constructor The constructor function for the Sink.
|
---|
131 | * @arg {Object} prototype The prototype of the Sink. (optional)
|
---|
132 | * @arg {Boolean} disabled Whether the Sink should be disabled at first.
|
---|
133 | */
|
---|
134 |
|
---|
135 | function sinks (type, constructor, prototype, disabled, priority) {
|
---|
136 | prototype = prototype || constructor.prototype;
|
---|
137 | constructor.prototype = new Sink.SinkClass();
|
---|
138 | constructor.prototype.type = type;
|
---|
139 | constructor.enabled = !disabled;
|
---|
140 |
|
---|
141 | var k;
|
---|
142 | for (k in prototype) {
|
---|
143 | if (prototype.hasOwnProperty(k)) {
|
---|
144 | constructor.prototype[k] = prototype[k];
|
---|
145 | }
|
---|
146 | }
|
---|
147 |
|
---|
148 | sinks[type] = constructor;
|
---|
149 | sinks.list[priority ? 'unshift' : 'push'](constructor);
|
---|
150 | }
|
---|
151 |
|
---|
152 | Sink.sinks = Sink.devices = sinks;
|
---|
153 | Sink.sinks.list = [];
|
---|
154 |
|
---|
155 | Sink.singleton = function () {
|
---|
156 | var sink = Sink.apply(null, arguments);
|
---|
157 |
|
---|
158 | Sink.singleton = function () {
|
---|
159 | return sink;
|
---|
160 | };
|
---|
161 |
|
---|
162 | return sink;
|
---|
163 | };
|
---|
164 |
|
---|
165 | global.Sink = Sink;
|
---|
166 |
|
---|
167 | return Sink;
|
---|
168 |
|
---|
169 | }(function (){ return this; }());
|
---|
170 | void function (Sink) {
|
---|
171 |
|
---|
172 | /**
|
---|
173 | * A light event emitter.
|
---|
174 | *
|
---|
175 | * @class
|
---|
176 | * @static Sink
|
---|
177 | */
|
---|
178 | function EventEmitter () {
|
---|
179 | var k;
|
---|
180 | for (k in EventEmitter.prototype) {
|
---|
181 | if (EventEmitter.prototype.hasOwnProperty(k)) {
|
---|
182 | this[k] = EventEmitter.prototype[k];
|
---|
183 | }
|
---|
184 | }
|
---|
185 | this._listeners = {};
|
---|
186 | }
|
---|
187 |
|
---|
188 | EventEmitter.prototype = {
|
---|
189 | _listeners: null,
|
---|
190 | /**
|
---|
191 | * Emits an event.
|
---|
192 | *
|
---|
193 | * @method EventEmitter
|
---|
194 | *
|
---|
195 | * @arg {String} name The name of the event to emit.
|
---|
196 | * @arg {Array} args The arguments to pass to the event handlers.
|
---|
197 | */
|
---|
198 | emit: function (name, args) {
|
---|
199 | if (this._listeners[name]) {
|
---|
200 | for (var i=0; i<this._listeners[name].length; i++) {
|
---|
201 | this._listeners[name][i].apply(this, args);
|
---|
202 | }
|
---|
203 | }
|
---|
204 | return this;
|
---|
205 | },
|
---|
206 | /**
|
---|
207 | * Adds an event listener to an event.
|
---|
208 | *
|
---|
209 | * @method EventEmitter
|
---|
210 | *
|
---|
211 | * @arg {String} name The name of the event.
|
---|
212 | * @arg {Function} listener The event listener to attach to the event.
|
---|
213 | */
|
---|
214 | on: function (name, listener) {
|
---|
215 | this._listeners[name] = this._listeners[name] || [];
|
---|
216 | this._listeners[name].push(listener);
|
---|
217 | return this;
|
---|
218 | },
|
---|
219 | /**
|
---|
220 | * Adds an event listener to an event.
|
---|
221 | *
|
---|
222 | * @method EventEmitter
|
---|
223 | *
|
---|
224 | * @arg {String} name The name of the event.
|
---|
225 | * @arg {Function} !listener The event listener to remove from the event. If not specified, will delete all.
|
---|
226 | */
|
---|
227 | off: function (name, listener) {
|
---|
228 | if (this._listeners[name]) {
|
---|
229 | if (!listener) {
|
---|
230 | delete this._listeners[name];
|
---|
231 | return this;
|
---|
232 | }
|
---|
233 |
|
---|
234 | for (var i=0; i<this._listeners[name].length; i++) {
|
---|
235 | if (this._listeners[name][i] === listener) {
|
---|
236 | this._listeners[name].splice(i--, 1);
|
---|
237 | }
|
---|
238 | }
|
---|
239 |
|
---|
240 | if (!this._listeners[name].length) {
|
---|
241 | delete this._listeners[name];
|
---|
242 | }
|
---|
243 | }
|
---|
244 | return this;
|
---|
245 | }
|
---|
246 | };
|
---|
247 |
|
---|
248 | Sink.EventEmitter = EventEmitter;
|
---|
249 |
|
---|
250 | EventEmitter.call(Sink);
|
---|
251 |
|
---|
252 | }(this.Sink);
|
---|
253 | void function (Sink) {
|
---|
254 |
|
---|
255 | /**
|
---|
256 | * Creates a timer with consistent (ie. not clamped) intervals even in background tabs.
|
---|
257 | * Uses inline workers to achieve this. If not available, will revert to regular timers.
|
---|
258 | *
|
---|
259 | * @static Sink
|
---|
260 | * @name doInterval
|
---|
261 | *
|
---|
262 | * @arg {Function} callback The callback to trigger on timer hit.
|
---|
263 | * @arg {Number} timeout The interval between timer hits.
|
---|
264 | *
|
---|
265 | * @return {Function} A function to cancel the timer.
|
---|
266 | */
|
---|
267 |
|
---|
268 | Sink.doInterval = function (callback, timeout) {
|
---|
269 | var timer, kill;
|
---|
270 |
|
---|
271 | function create (noWorker) {
|
---|
272 | if (Sink.inlineWorker.working && !noWorker) {
|
---|
273 | timer = Sink.inlineWorker('setInterval(function (){ postMessage("tic"); }, ' + timeout + ');');
|
---|
274 | timer.onmessage = function (){
|
---|
275 | callback();
|
---|
276 | };
|
---|
277 | kill = function () {
|
---|
278 | timer.terminate();
|
---|
279 | };
|
---|
280 | } else {
|
---|
281 | timer = setInterval(callback, timeout);
|
---|
282 | kill = function (){
|
---|
283 | clearInterval(timer);
|
---|
284 | };
|
---|
285 | }
|
---|
286 | }
|
---|
287 |
|
---|
288 | if (Sink.inlineWorker.ready) {
|
---|
289 | create();
|
---|
290 | } else {
|
---|
291 | Sink.inlineWorker.on('ready', function () {
|
---|
292 | create();
|
---|
293 | });
|
---|
294 | }
|
---|
295 |
|
---|
296 | return function () {
|
---|
297 | if (!kill) {
|
---|
298 | if (!Sink.inlineWorker.ready) {
|
---|
299 | Sink.inlineWorker.on('ready', function () {
|
---|
300 | if (kill) kill();
|
---|
301 | });
|
---|
302 | }
|
---|
303 | } else {
|
---|
304 | kill();
|
---|
305 | }
|
---|
306 | };
|
---|
307 | };
|
---|
308 |
|
---|
309 | }(this.Sink);
|
---|
310 | void function (Sink) {
|
---|
311 |
|
---|
312 | var _Blob, _BlobBuilder, _URL, _btoa;
|
---|
313 |
|
---|
314 | void function (prefixes, urlPrefixes) {
|
---|
315 | function find (name, prefixes) {
|
---|
316 | var b, a = prefixes.slice();
|
---|
317 |
|
---|
318 | for (b=a.shift(); typeof b !== 'undefined'; b=a.shift()) {
|
---|
319 | b = Function('return typeof ' + b + name +
|
---|
320 | '=== "undefined" ? undefined : ' +
|
---|
321 | b + name)();
|
---|
322 |
|
---|
323 | if (b) return b;
|
---|
324 | }
|
---|
325 | }
|
---|
326 |
|
---|
327 | _Blob = find('Blob', prefixes);
|
---|
328 | _BlobBuilder = find('BlobBuilder', prefixes);
|
---|
329 | _URL = find('URL', urlPrefixes);
|
---|
330 | _btoa = find('btoa', ['']);
|
---|
331 | }([
|
---|
332 | '',
|
---|
333 | 'Moz',
|
---|
334 | 'WebKit',
|
---|
335 | 'MS'
|
---|
336 | ], [
|
---|
337 | '',
|
---|
338 | 'webkit'
|
---|
339 | ]);
|
---|
340 |
|
---|
341 | var createBlob = _Blob && _URL && function (content, type) {
|
---|
342 | return _URL.createObjectURL(new _Blob([content], { type: type }));
|
---|
343 | };
|
---|
344 |
|
---|
345 | var createBlobBuilder = _BlobBuilder && _URL && function (content, type) {
|
---|
346 | var bb = new _BlobBuilder();
|
---|
347 | bb.append(content);
|
---|
348 |
|
---|
349 | return _URL.createObjectURL(bb.getBlob(type));
|
---|
350 | };
|
---|
351 |
|
---|
352 | var createData = _btoa && function (content, type) {
|
---|
353 | return 'data:' + type + ';base64,' + _btoa(content);
|
---|
354 | };
|
---|
355 |
|
---|
356 | var createDynURL =
|
---|
357 | createBlob ||
|
---|
358 | createBlobBuilder ||
|
---|
359 | createData;
|
---|
360 |
|
---|
361 | if (!createDynURL) return;
|
---|
362 |
|
---|
363 | if (createBlob) createDynURL.createBlob = createBlob;
|
---|
364 | if (createBlobBuilder) createDynURL.createBlobBuilder = createBlobBuilder;
|
---|
365 | if (createData) createDynURL.createData = createData;
|
---|
366 |
|
---|
367 | if (_Blob) createDynURL.Blob = _Blob;
|
---|
368 | if (_BlobBuilder) createDynURL.BlobBuilder = _BlobBuilder;
|
---|
369 | if (_URL) createDynURL.URL = _URL;
|
---|
370 |
|
---|
371 | Sink.createDynURL = createDynURL;
|
---|
372 |
|
---|
373 | Sink.revokeDynURL = function (url) {
|
---|
374 | if (typeof url === 'string' && url.indexOf('data:') === 0) {
|
---|
375 | return false;
|
---|
376 | } else {
|
---|
377 | return _URL.revokeObjectURL(url);
|
---|
378 | }
|
---|
379 | };
|
---|
380 |
|
---|
381 | }(this.Sink);
|
---|
382 | void function (Sink) {
|
---|
383 |
|
---|
384 | /*
|
---|
385 | * A Sink-specific error class.
|
---|
386 | *
|
---|
387 | * @class
|
---|
388 | * @static Sink
|
---|
389 | * @name Error
|
---|
390 | *
|
---|
391 | * @arg =code
|
---|
392 | *
|
---|
393 | * @param {Number} code The error code.
|
---|
394 | * @param {String} message A brief description of the error.
|
---|
395 | * @param {String} explanation A more verbose explanation of why the error occured and how to fix.
|
---|
396 | */
|
---|
397 |
|
---|
398 | function SinkError(code) {
|
---|
399 | if (!SinkError.hasOwnProperty(code)) throw SinkError(1);
|
---|
400 | if (!(this instanceof SinkError)) return new SinkError(code);
|
---|
401 |
|
---|
402 | var k;
|
---|
403 | for (k in SinkError[code]) {
|
---|
404 | if (SinkError[code].hasOwnProperty(k)) {
|
---|
405 | this[k] = SinkError[code][k];
|
---|
406 | }
|
---|
407 | }
|
---|
408 |
|
---|
409 | this.code = code;
|
---|
410 | }
|
---|
411 |
|
---|
412 | SinkError.prototype = new Error();
|
---|
413 |
|
---|
414 | SinkError.prototype.toString = function () {
|
---|
415 | return 'SinkError 0x' + this.code.toString(16) + ': ' + this.message;
|
---|
416 | };
|
---|
417 |
|
---|
418 | SinkError[0x01] = {
|
---|
419 | message: 'No such error code.',
|
---|
420 | explanation: 'The error code does not exist.'
|
---|
421 | };
|
---|
422 | SinkError[0x02] = {
|
---|
423 | message: 'No audio sink available.',
|
---|
424 | explanation: 'The audio device may be busy, or no supported output API is available for this browser.'
|
---|
425 | };
|
---|
426 |
|
---|
427 | SinkError[0x10] = {
|
---|
428 | message: 'Buffer underflow.',
|
---|
429 | explanation: 'Trying to recover...'
|
---|
430 | };
|
---|
431 | SinkError[0x11] = {
|
---|
432 | message: 'Critical recovery fail.',
|
---|
433 | explanation: 'The buffer underflow has reached a critical point, trying to recover, but will probably fail anyway.'
|
---|
434 | };
|
---|
435 | SinkError[0x12] = {
|
---|
436 | message: 'Buffer size too large.',
|
---|
437 | explanation: 'Unable to allocate the buffer due to excessive length, please try a smaller buffer. Buffer size should probably be smaller than the sample rate.'
|
---|
438 | };
|
---|
439 |
|
---|
440 | Sink.Error = SinkError;
|
---|
441 |
|
---|
442 | }(this.Sink);
|
---|
443 | void function (Sink) {
|
---|
444 |
|
---|
445 | /**
|
---|
446 | * Creates an inline worker using a data/blob URL, if possible.
|
---|
447 | *
|
---|
448 | * @static Sink
|
---|
449 | *
|
---|
450 | * @arg {String} script
|
---|
451 | *
|
---|
452 | * @return {Worker} A web worker, or null if impossible to create.
|
---|
453 | */
|
---|
454 |
|
---|
455 | var define = Object.defineProperty ? function (obj, name, value) {
|
---|
456 | Object.defineProperty(obj, name, {
|
---|
457 | value: value,
|
---|
458 | configurable: true,
|
---|
459 | writable: true
|
---|
460 | });
|
---|
461 | } : function (obj, name, value) {
|
---|
462 | obj[name] = value;
|
---|
463 | };
|
---|
464 |
|
---|
465 | function terminate () {
|
---|
466 | define(this, 'terminate', this._terminate);
|
---|
467 |
|
---|
468 | Sink.revokeDynURL(this._url);
|
---|
469 |
|
---|
470 | delete this._url;
|
---|
471 | delete this._terminate;
|
---|
472 | return this.terminate();
|
---|
473 | }
|
---|
474 |
|
---|
475 | function inlineWorker (script) {
|
---|
476 | function wrap (type, content, typeName) {
|
---|
477 | try {
|
---|
478 | var url = type(content, 'text/javascript');
|
---|
479 | var worker = new Worker(url);
|
---|
480 |
|
---|
481 | define(worker, '_url', url);
|
---|
482 | define(worker, '_terminate', worker.terminate);
|
---|
483 | define(worker, 'terminate', terminate);
|
---|
484 |
|
---|
485 | if (inlineWorker.type) return worker;
|
---|
486 |
|
---|
487 | inlineWorker.type = typeName;
|
---|
488 | inlineWorker.createURL = type;
|
---|
489 |
|
---|
490 | return worker;
|
---|
491 | } catch (e) {
|
---|
492 | return null;
|
---|
493 | }
|
---|
494 | }
|
---|
495 |
|
---|
496 | var createDynURL = Sink.createDynURL;
|
---|
497 |
|
---|
498 | if (!createDynURL) return null;
|
---|
499 |
|
---|
500 | var worker;
|
---|
501 |
|
---|
502 | if (inlineWorker.createURL) {
|
---|
503 | return wrap(inlineWorker.createURL, script, inlineWorker.type);
|
---|
504 | }
|
---|
505 |
|
---|
506 | worker = wrap(createDynURL.createBlob, script, 'blob');
|
---|
507 | if (worker) return worker;
|
---|
508 |
|
---|
509 | worker = wrap(createDynURL.createBlobBuilder, script, 'blobbuilder');
|
---|
510 | if (worker) return worker;
|
---|
511 |
|
---|
512 | worker = wrap(createDynURL.createData, script, 'data');
|
---|
513 |
|
---|
514 | return worker;
|
---|
515 | }
|
---|
516 |
|
---|
517 | Sink.EventEmitter.call(inlineWorker);
|
---|
518 |
|
---|
519 | inlineWorker.test = function () {
|
---|
520 | inlineWorker.ready = inlineWorker.working = false;
|
---|
521 | inlineWorker.type = '';
|
---|
522 | inlineWorker.createURL = null;
|
---|
523 |
|
---|
524 | var worker = inlineWorker('this.onmessage=function(e){postMessage(e.data)}');
|
---|
525 | var data = 'inlineWorker';
|
---|
526 |
|
---|
527 | function ready (success) {
|
---|
528 | if (inlineWorker.ready) return;
|
---|
529 |
|
---|
530 | inlineWorker.ready = true;
|
---|
531 | inlineWorker.working = success;
|
---|
532 | inlineWorker.emit('ready', [success]);
|
---|
533 | inlineWorker.off('ready');
|
---|
534 |
|
---|
535 | if (success && worker) {
|
---|
536 | worker.terminate();
|
---|
537 | }
|
---|
538 |
|
---|
539 | worker = null;
|
---|
540 | }
|
---|
541 |
|
---|
542 | if (!worker) {
|
---|
543 | setTimeout(function () {
|
---|
544 | ready(false);
|
---|
545 | }, 0);
|
---|
546 | } else {
|
---|
547 | worker.onmessage = function (e) {
|
---|
548 | ready(e.data === data);
|
---|
549 | };
|
---|
550 |
|
---|
551 | worker.postMessage(data);
|
---|
552 |
|
---|
553 | setTimeout(function () {
|
---|
554 | ready(false);
|
---|
555 | }, 1000);
|
---|
556 | }
|
---|
557 | };
|
---|
558 |
|
---|
559 | Sink.inlineWorker = inlineWorker;
|
---|
560 |
|
---|
561 | inlineWorker.test();
|
---|
562 |
|
---|
563 | }(this.Sink);
|
---|
564 | void function (Sink) {
|
---|
565 |
|
---|
566 | /**
|
---|
567 | * A Sink class for the Mozilla Audio Data API.
|
---|
568 | */
|
---|
569 |
|
---|
570 | Sink.sinks('audiodata', function () {
|
---|
571 | var self = this,
|
---|
572 | currentWritePosition = 0,
|
---|
573 | tail = null,
|
---|
574 | audioDevice = new Audio(),
|
---|
575 | written, currentPosition, available, soundData, prevPos,
|
---|
576 | timer; // Fix for https://bugzilla.mozilla.org/show_bug.cgi?id=630117
|
---|
577 | self.start.apply(self, arguments);
|
---|
578 | self.preBufferSize = isNaN(arguments[4]) || arguments[4] === null ? this.preBufferSize : arguments[4];
|
---|
579 |
|
---|
580 | function bufferFill() {
|
---|
581 | if (tail) {
|
---|
582 | written = audioDevice.mozWriteAudio(tail);
|
---|
583 | currentWritePosition += written;
|
---|
584 | if (written < tail.length){
|
---|
585 | tail = tail.subarray(written);
|
---|
586 | return tail;
|
---|
587 | }
|
---|
588 | tail = null;
|
---|
589 | }
|
---|
590 |
|
---|
591 | currentPosition = audioDevice.mozCurrentSampleOffset();
|
---|
592 | available = Number(currentPosition + (prevPos !== currentPosition ? self.bufferSize : self.preBufferSize) * self.channelCount - currentWritePosition);
|
---|
593 |
|
---|
594 | if (currentPosition === prevPos) {
|
---|
595 | self.emit('error', [Sink.Error(0x10)]);
|
---|
596 | }
|
---|
597 |
|
---|
598 | if (available > 0 || prevPos === currentPosition){
|
---|
599 | self.ready();
|
---|
600 |
|
---|
601 | try {
|
---|
602 | soundData = new Float32Array(prevPos === currentPosition ? self.preBufferSize * self.channelCount :
|
---|
603 | self.forceBufferSize ? available < self.bufferSize * 2 ? self.bufferSize * 2 : available : available);
|
---|
604 | } catch(e) {
|
---|
605 | self.emit('error', [Sink.Error(0x12)]);
|
---|
606 | self.kill();
|
---|
607 | return;
|
---|
608 | }
|
---|
609 | self.process(soundData, self.channelCount);
|
---|
610 | written = self._audio.mozWriteAudio(soundData);
|
---|
611 | if (written < soundData.length){
|
---|
612 | tail = soundData.subarray(written);
|
---|
613 | }
|
---|
614 | currentWritePosition += written;
|
---|
615 | }
|
---|
616 | prevPos = currentPosition;
|
---|
617 | }
|
---|
618 |
|
---|
619 | audioDevice.mozSetup(self.channelCount, self.sampleRate);
|
---|
620 |
|
---|
621 | this._timers = [];
|
---|
622 |
|
---|
623 | this._timers.push(Sink.doInterval(function () {
|
---|
624 | // Check for complete death of the output
|
---|
625 | if (+new Date() - self.previousHit > 2000) {
|
---|
626 | self._audio = audioDevice = new Audio();
|
---|
627 | audioDevice.mozSetup(self.channelCount, self.sampleRate);
|
---|
628 | currentWritePosition = 0;
|
---|
629 | self.emit('error', [Sink.Error(0x11)]);
|
---|
630 | }
|
---|
631 | }, 1000));
|
---|
632 |
|
---|
633 | this._timers.push(Sink.doInterval(bufferFill, self.interval));
|
---|
634 |
|
---|
635 | self._bufferFill = bufferFill;
|
---|
636 | self._audio = audioDevice;
|
---|
637 | }, {
|
---|
638 | // These are somewhat safe values...
|
---|
639 | bufferSize: 24576,
|
---|
640 | preBufferSize: 24576,
|
---|
641 | forceBufferSize: false,
|
---|
642 | interval: 100,
|
---|
643 |
|
---|
644 | kill: function () {
|
---|
645 | while (this._timers.length) {
|
---|
646 | this._timers.shift()();
|
---|
647 | }
|
---|
648 |
|
---|
649 | this.emit('kill');
|
---|
650 | },
|
---|
651 |
|
---|
652 | getPlaybackTime: function () {
|
---|
653 | return this._audio.mozCurrentSampleOffset() / this.channelCount;
|
---|
654 | }
|
---|
655 | }, false, true);
|
---|
656 |
|
---|
657 | Sink.sinks.moz = Sink.sinks.audiodata;
|
---|
658 |
|
---|
659 | }(this.Sink);
|
---|
660 | void function (Sink) {
|
---|
661 |
|
---|
662 | var cubeb;
|
---|
663 |
|
---|
664 | try {
|
---|
665 | cubeb = require('cubeb');
|
---|
666 | } catch (e) {
|
---|
667 | return;
|
---|
668 | }
|
---|
669 |
|
---|
670 | var getContext = function () {
|
---|
671 | var ctx;
|
---|
672 |
|
---|
673 | return function () {
|
---|
674 | ctx = new cubeb.Context(
|
---|
675 | "sink.js " + process.pid + ' ' + new Date()
|
---|
676 | );
|
---|
677 |
|
---|
678 | getContext = function () { return ctx; };
|
---|
679 |
|
---|
680 | return ctx;
|
---|
681 | };
|
---|
682 | }();
|
---|
683 |
|
---|
684 | var streamCount = 0;
|
---|
685 |
|
---|
686 | Sink.sinks('cubeb', function () {
|
---|
687 | var self = this;
|
---|
688 |
|
---|
689 | self.start.apply(self, arguments);
|
---|
690 |
|
---|
691 | self._ctx = getContext();
|
---|
692 | self._stream = new cubeb.Stream(
|
---|
693 | self._ctx,
|
---|
694 | self._ctx.name + ' ' + streamCount++,
|
---|
695 | cubeb.SAMPLE_FLOAT32LE,
|
---|
696 | self.channelCount,
|
---|
697 | self.sampleRate,
|
---|
698 | self.bufferSize,
|
---|
699 | self._latency,
|
---|
700 | function (frameCount) {
|
---|
701 | var buffer = new Buffer(
|
---|
702 | 4 * frameCount * self.channelCount);
|
---|
703 | var soundData = new Float32Array(buffer);
|
---|
704 |
|
---|
705 | self.process(soundData, self.channelCount);
|
---|
706 |
|
---|
707 | self._stream.write(buffer);
|
---|
708 | self._stream.release();
|
---|
709 | },
|
---|
710 | function (state) {}
|
---|
711 | );
|
---|
712 |
|
---|
713 | self._stream.start();
|
---|
714 | }, {
|
---|
715 | _ctx: null,
|
---|
716 | _stream: null,
|
---|
717 | _latency: 250,
|
---|
718 | bufferSize: 4096,
|
---|
719 |
|
---|
720 | kill: function () {
|
---|
721 | this._stream.stop();
|
---|
722 | this.emit('kill');
|
---|
723 | },
|
---|
724 |
|
---|
725 | getPlaybackTime: function () {
|
---|
726 | return this._stream.position;
|
---|
727 | }
|
---|
728 | });
|
---|
729 |
|
---|
730 | }(this.Sink);
|
---|
731 | void function (Sink) {
|
---|
732 |
|
---|
733 | /**
|
---|
734 | * A dummy Sink. (No output)
|
---|
735 | */
|
---|
736 |
|
---|
737 | Sink.sinks('dummy', function () {
|
---|
738 | var self = this;
|
---|
739 | self.start.apply(self, arguments);
|
---|
740 |
|
---|
741 | function bufferFill () {
|
---|
742 | var soundData = new Float32Array(self.bufferSize * self.channelCount);
|
---|
743 | self.process(soundData, self.channelCount);
|
---|
744 | }
|
---|
745 |
|
---|
746 | self._kill = Sink.doInterval(bufferFill, self.bufferSize / self.sampleRate * 1000);
|
---|
747 |
|
---|
748 | self._callback = bufferFill;
|
---|
749 | }, {
|
---|
750 | kill: function () {
|
---|
751 | this._kill();
|
---|
752 | this.emit('kill');
|
---|
753 | }
|
---|
754 | }, true);
|
---|
755 |
|
---|
756 | }(this.Sink);
|
---|
757 | (function (Sink, sinks) {
|
---|
758 |
|
---|
759 | sinks = Sink.sinks;
|
---|
760 |
|
---|
761 | function newAudio (src) {
|
---|
762 | var audio = document.createElement('audio');
|
---|
763 | if (src) {
|
---|
764 | audio.src = src;
|
---|
765 | }
|
---|
766 | return audio;
|
---|
767 | }
|
---|
768 |
|
---|
769 | /* TODO: Implement a <BGSOUND> hack for IE8. */
|
---|
770 |
|
---|
771 | /**
|
---|
772 | * A sink class for WAV data URLs
|
---|
773 | * Relies on pcmdata.js and utils to be present.
|
---|
774 | * Thanks to grantgalitz and others for the idea.
|
---|
775 | */
|
---|
776 | sinks('wav', function () {
|
---|
777 | var self = this,
|
---|
778 | audio = new sinks.wav.wavAudio(),
|
---|
779 | PCMData = typeof PCMData === 'undefined' ? audioLib.PCMData : PCMData;
|
---|
780 | self.start.apply(self, arguments);
|
---|
781 | var soundData = new Float32Array(self.bufferSize * self.channelCount),
|
---|
782 | zeroData = new Float32Array(self.bufferSize * self.channelCount);
|
---|
783 |
|
---|
784 | if (!newAudio().canPlayType('audio/wav; codecs=1') || !btoa) throw 0;
|
---|
785 |
|
---|
786 | function bufferFill () {
|
---|
787 | if (self._audio.hasNextFrame) return;
|
---|
788 |
|
---|
789 | self.ready();
|
---|
790 |
|
---|
791 | Sink.memcpy(zeroData, 0, soundData, 0);
|
---|
792 | self.process(soundData, self.channelCount);
|
---|
793 |
|
---|
794 | self._audio.setSource('data:audio/wav;base64,' + btoa(
|
---|
795 | audioLib.PCMData.encode({
|
---|
796 | data: soundData,
|
---|
797 | sampleRate: self.sampleRate,
|
---|
798 | channelCount: self.channelCount,
|
---|
799 | bytesPerSample: self.quality
|
---|
800 | })
|
---|
801 | ));
|
---|
802 |
|
---|
803 | if (!self._audio.currentFrame.src) self._audio.nextClip();
|
---|
804 | }
|
---|
805 |
|
---|
806 | self.kill = Sink.doInterval(bufferFill, 40);
|
---|
807 | self._bufferFill = bufferFill;
|
---|
808 | self._audio = audio;
|
---|
809 | }, {
|
---|
810 | quality: 1,
|
---|
811 | bufferSize: 22050,
|
---|
812 |
|
---|
813 | getPlaybackTime: function () {
|
---|
814 | var audio = this._audio;
|
---|
815 | return (audio.currentFrame ? audio.currentFrame.currentTime * this.sampleRate : 0) + audio.samples;
|
---|
816 | }
|
---|
817 | });
|
---|
818 |
|
---|
819 | function wavAudio () {
|
---|
820 | var self = this;
|
---|
821 |
|
---|
822 | self.currentFrame = newAudio();
|
---|
823 | self.nextFrame = newAudio();
|
---|
824 |
|
---|
825 | self._onended = function () {
|
---|
826 | self.samples += self.bufferSize;
|
---|
827 | self.nextClip();
|
---|
828 | };
|
---|
829 | }
|
---|
830 |
|
---|
831 | wavAudio.prototype = {
|
---|
832 | samples: 0,
|
---|
833 | nextFrame: null,
|
---|
834 | currentFrame: null,
|
---|
835 | _onended: null,
|
---|
836 | hasNextFrame: false,
|
---|
837 |
|
---|
838 | nextClip: function () {
|
---|
839 | var curFrame = this.currentFrame;
|
---|
840 | this.currentFrame = this.nextFrame;
|
---|
841 | this.nextFrame = curFrame;
|
---|
842 | this.hasNextFrame = false;
|
---|
843 | this.currentFrame.play();
|
---|
844 | },
|
---|
845 |
|
---|
846 | setSource: function (src) {
|
---|
847 | this.nextFrame.src = src;
|
---|
848 | this.nextFrame.addEventListener('ended', this._onended, true);
|
---|
849 |
|
---|
850 | this.hasNextFrame = true;
|
---|
851 | }
|
---|
852 | };
|
---|
853 |
|
---|
854 | sinks.wav.wavAudio = wavAudio;
|
---|
855 |
|
---|
856 | }(this.Sink));
|
---|
857 | (function (sinks, fixChrome82795) {
|
---|
858 |
|
---|
859 | var AudioContext = typeof window === 'undefined' ? null : window.webkitAudioContext || window.AudioContext;
|
---|
860 |
|
---|
861 | /**
|
---|
862 | * A sink class for the Web Audio API
|
---|
863 | */
|
---|
864 |
|
---|
865 | sinks('webaudio', function (readFn, channelCount, bufferSize, sampleRate) {
|
---|
866 | var self = this,
|
---|
867 | context = sinks.webaudio.getContext(),
|
---|
868 | node = null,
|
---|
869 | soundData = null,
|
---|
870 | zeroBuffer = null;
|
---|
871 | self.start.apply(self, arguments);
|
---|
872 | node = context.createJavaScriptNode(self.bufferSize, self.channelCount, self.channelCount);
|
---|
873 |
|
---|
874 | function bufferFill(e) {
|
---|
875 | var outputBuffer = e.outputBuffer,
|
---|
876 | channelCount = outputBuffer.numberOfChannels,
|
---|
877 | i, n, l = outputBuffer.length,
|
---|
878 | size = outputBuffer.size,
|
---|
879 | channels = new Array(channelCount),
|
---|
880 | tail;
|
---|
881 |
|
---|
882 | self.ready();
|
---|
883 |
|
---|
884 | soundData = soundData && soundData.length === l * channelCount ? soundData : new Float32Array(l * channelCount);
|
---|
885 | zeroBuffer = zeroBuffer && zeroBuffer.length === soundData.length ? zeroBuffer : new Float32Array(l * channelCount);
|
---|
886 | soundData.set(zeroBuffer);
|
---|
887 |
|
---|
888 | for (i=0; i<channelCount; i++) {
|
---|
889 | channels[i] = outputBuffer.getChannelData(i);
|
---|
890 | }
|
---|
891 |
|
---|
892 | self.process(soundData, self.channelCount);
|
---|
893 |
|
---|
894 | for (i=0; i<l; i++) {
|
---|
895 | for (n=0; n < channelCount; n++) {
|
---|
896 | channels[n][i] = soundData[i * self.channelCount + n];
|
---|
897 | }
|
---|
898 | }
|
---|
899 | }
|
---|
900 |
|
---|
901 | self.sampleRate = context.sampleRate;
|
---|
902 |
|
---|
903 | node.onaudioprocess = bufferFill;
|
---|
904 | node.connect(context.destination);
|
---|
905 |
|
---|
906 | self._context = context;
|
---|
907 | self._node = node;
|
---|
908 | self._callback = bufferFill;
|
---|
909 | /* Keep references in order to avoid garbage collection removing the listeners, working around http://code.google.com/p/chromium/issues/detail?id=82795 */
|
---|
910 | // Thanks to @baffo32
|
---|
911 | fixChrome82795.push(node);
|
---|
912 | }, {
|
---|
913 | kill: function () {
|
---|
914 | this._node.disconnect(0);
|
---|
915 |
|
---|
916 | for (var i=0; i<fixChrome82795.length; i++) {
|
---|
917 | if (fixChrome82795[i] === this._node) {
|
---|
918 | fixChrome82795.splice(i--, 1);
|
---|
919 | }
|
---|
920 | }
|
---|
921 |
|
---|
922 | this._node = this._context = null;
|
---|
923 | this.emit('kill');
|
---|
924 | },
|
---|
925 |
|
---|
926 | getPlaybackTime: function () {
|
---|
927 | return this._context.currentTime * this.sampleRate;
|
---|
928 | }
|
---|
929 | }, false, true);
|
---|
930 |
|
---|
931 | sinks.webkit = sinks.webaudio;
|
---|
932 |
|
---|
933 | sinks.webaudio.fix82795 = fixChrome82795;
|
---|
934 |
|
---|
935 | sinks.webaudio.getContext = function () {
|
---|
936 | // For now, we have to accept that the AudioContext is at 48000Hz, or whatever it decides.
|
---|
937 | var context = new AudioContext(/*sampleRate*/);
|
---|
938 |
|
---|
939 | sinks.webaudio.getContext = function () {
|
---|
940 | return context;
|
---|
941 | };
|
---|
942 |
|
---|
943 | return context;
|
---|
944 | };
|
---|
945 |
|
---|
946 | }(this.Sink.sinks, []));
|
---|
947 | (function (Sink) {
|
---|
948 |
|
---|
949 | /**
|
---|
950 | * A Sink class for the Media Streams Processing API and/or Web Audio API in a Web Worker.
|
---|
951 | */
|
---|
952 |
|
---|
953 | Sink.sinks('worker', function () {
|
---|
954 | var self = this,
|
---|
955 | global = (function(){ return this; }()),
|
---|
956 | soundData = null,
|
---|
957 | outBuffer = null,
|
---|
958 | zeroBuffer = null;
|
---|
959 | self.start.apply(self, arguments);
|
---|
960 |
|
---|
961 | // Let's see if we're in a worker.
|
---|
962 |
|
---|
963 | importScripts();
|
---|
964 |
|
---|
965 | function mspBufferFill (e) {
|
---|
966 | if (!self.isReady) {
|
---|
967 | self.initMSP(e);
|
---|
968 | }
|
---|
969 |
|
---|
970 | self.ready();
|
---|
971 |
|
---|
972 | var channelCount = self.channelCount,
|
---|
973 | l = e.audioLength,
|
---|
974 | n, i;
|
---|
975 |
|
---|
976 | soundData = soundData && soundData.length === l * channelCount ? soundData : new Float32Array(l * channelCount);
|
---|
977 | outBuffer = outBuffer && outBuffer.length === soundData.length ? outBuffer : new Float32Array(l * channelCount);
|
---|
978 | zeroBuffer = zeroBuffer && zeroBuffer.length === soundData.length ? zeroBuffer : new Float32Array(l * channelCount);
|
---|
979 |
|
---|
980 | soundData.set(zeroBuffer);
|
---|
981 | outBuffer.set(zeroBuffer);
|
---|
982 |
|
---|
983 | self.process(soundData, self.channelCount);
|
---|
984 |
|
---|
985 | for (n=0; n<channelCount; n++) {
|
---|
986 | for (i=0; i<l; i++) {
|
---|
987 | outBuffer[n * e.audioLength + i] = soundData[n + i * channelCount];
|
---|
988 | }
|
---|
989 | }
|
---|
990 |
|
---|
991 | e.writeAudio(outBuffer);
|
---|
992 | }
|
---|
993 |
|
---|
994 | function waBufferFill(e) {
|
---|
995 | if (!self.isReady) {
|
---|
996 | self.initWA(e);
|
---|
997 | }
|
---|
998 |
|
---|
999 | self.ready();
|
---|
1000 |
|
---|
1001 | var outputBuffer = e.outputBuffer,
|
---|
1002 | channelCount = outputBuffer.numberOfChannels,
|
---|
1003 | i, n, l = outputBuffer.length,
|
---|
1004 | size = outputBuffer.size,
|
---|
1005 | channels = new Array(channelCount),
|
---|
1006 | tail;
|
---|
1007 |
|
---|
1008 | soundData = soundData && soundData.length === l * channelCount ? soundData : new Float32Array(l * channelCount);
|
---|
1009 | zeroBuffer = zeroBuffer && zeroBuffer.length === soundData.length ? zeroBuffer : new Float32Array(l * channelCount);
|
---|
1010 | soundData.set(zeroBuffer);
|
---|
1011 |
|
---|
1012 | for (i=0; i<channelCount; i++) {
|
---|
1013 | channels[i] = outputBuffer.getChannelData(i);
|
---|
1014 | }
|
---|
1015 |
|
---|
1016 | self.process(soundData, self.channelCount);
|
---|
1017 |
|
---|
1018 | for (i=0; i<l; i++) {
|
---|
1019 | for (n=0; n < channelCount; n++) {
|
---|
1020 | channels[n][i] = soundData[i * self.channelCount + n];
|
---|
1021 | }
|
---|
1022 | }
|
---|
1023 | }
|
---|
1024 |
|
---|
1025 | global.onprocessmedia = mspBufferFill;
|
---|
1026 | global.onaudioprocess = waBufferFill;
|
---|
1027 |
|
---|
1028 | self._mspBufferFill = mspBufferFill;
|
---|
1029 | self._waBufferFill = waBufferFill;
|
---|
1030 |
|
---|
1031 | }, {
|
---|
1032 | ready: false,
|
---|
1033 |
|
---|
1034 | initMSP: function (e) {
|
---|
1035 | this.channelCount = e.audioChannels;
|
---|
1036 | this.sampleRate = e.audioSampleRate;
|
---|
1037 | this.bufferSize = e.audioLength * this.channelCount;
|
---|
1038 | this.ready = true;
|
---|
1039 | this.emit('ready', []);
|
---|
1040 | },
|
---|
1041 |
|
---|
1042 | initWA: function (e) {
|
---|
1043 | var b = e.outputBuffer;
|
---|
1044 | this.channelCount = b.numberOfChannels;
|
---|
1045 | this.sampleRate = b.sampleRate;
|
---|
1046 | this.bufferSize = b.length * this.channelCount;
|
---|
1047 | this.ready = true;
|
---|
1048 | this.emit('ready', []);
|
---|
1049 | }
|
---|
1050 | });
|
---|
1051 |
|
---|
1052 | }(this.Sink));
|
---|
1053 | (function (Sink) {
|
---|
1054 |
|
---|
1055 | /**
|
---|
1056 | * Splits a sample buffer into those of different channels.
|
---|
1057 | *
|
---|
1058 | * @static Sink
|
---|
1059 | * @name deinterleave
|
---|
1060 | *
|
---|
1061 | * @arg {Buffer} buffer The sample buffer to split.
|
---|
1062 | * @arg {Number} channelCount The number of channels to split to.
|
---|
1063 | *
|
---|
1064 | * @return {Array} An array containing the resulting sample buffers.
|
---|
1065 | */
|
---|
1066 |
|
---|
1067 | Sink.deinterleave = function (buffer, channelCount) {
|
---|
1068 | var l = buffer.length,
|
---|
1069 | size = l / channelCount,
|
---|
1070 | ret = [],
|
---|
1071 | i, n;
|
---|
1072 | for (i=0; i<channelCount; i++){
|
---|
1073 | ret[i] = new Float32Array(size);
|
---|
1074 | for (n=0; n<size; n++){
|
---|
1075 | ret[i][n] = buffer[n * channelCount + i];
|
---|
1076 | }
|
---|
1077 | }
|
---|
1078 | return ret;
|
---|
1079 | };
|
---|
1080 |
|
---|
1081 | /**
|
---|
1082 | * Joins an array of sample buffers into a single buffer.
|
---|
1083 | *
|
---|
1084 | * @static Sink
|
---|
1085 | * @name resample
|
---|
1086 | *
|
---|
1087 | * @arg {Array} buffers The buffers to join.
|
---|
1088 | * @arg {Number} !channelCount The number of channels. Defaults to buffers.length
|
---|
1089 | * @arg {Buffer} !buffer The output buffer.
|
---|
1090 | *
|
---|
1091 | * @return {Buffer} The interleaved buffer created.
|
---|
1092 | */
|
---|
1093 |
|
---|
1094 | Sink.interleave = function (buffers, channelCount, buffer) {
|
---|
1095 | channelCount = channelCount || buffers.length;
|
---|
1096 | var l = buffers[0].length,
|
---|
1097 | bufferCount = buffers.length,
|
---|
1098 | i, n;
|
---|
1099 | buffer = buffer || new Float32Array(l * channelCount);
|
---|
1100 | for (i=0; i<bufferCount; i++) {
|
---|
1101 | for (n=0; n<l; n++) {
|
---|
1102 | buffer[i + n * channelCount] = buffers[i][n];
|
---|
1103 | }
|
---|
1104 | }
|
---|
1105 | return buffer;
|
---|
1106 | };
|
---|
1107 |
|
---|
1108 | /**
|
---|
1109 | * Mixes two or more buffers down to one.
|
---|
1110 | *
|
---|
1111 | * @static Sink
|
---|
1112 | * @name mix
|
---|
1113 | *
|
---|
1114 | * @arg {Buffer} buffer The buffer to append the others to.
|
---|
1115 | * @arg {Buffer} bufferX The buffers to append from.
|
---|
1116 | *
|
---|
1117 | * @return {Buffer} The mixed buffer.
|
---|
1118 | */
|
---|
1119 |
|
---|
1120 | Sink.mix = function (buffer) {
|
---|
1121 | var buffers = [].slice.call(arguments, 1),
|
---|
1122 | l, i, c;
|
---|
1123 | for (c=0; c<buffers.length; c++){
|
---|
1124 | l = Math.max(buffer.length, buffers[c].length);
|
---|
1125 | for (i=0; i<l; i++){
|
---|
1126 | buffer[i] += buffers[c][i];
|
---|
1127 | }
|
---|
1128 | }
|
---|
1129 | return buffer;
|
---|
1130 | };
|
---|
1131 |
|
---|
1132 | /**
|
---|
1133 | * Resets a buffer to all zeroes.
|
---|
1134 | *
|
---|
1135 | * @static Sink
|
---|
1136 | * @name resetBuffer
|
---|
1137 | *
|
---|
1138 | * @arg {Buffer} buffer The buffer to reset.
|
---|
1139 | *
|
---|
1140 | * @return {Buffer} The 0-reset buffer.
|
---|
1141 | */
|
---|
1142 |
|
---|
1143 | Sink.resetBuffer = function (buffer) {
|
---|
1144 | var l = buffer.length,
|
---|
1145 | i;
|
---|
1146 | for (i=0; i<l; i++){
|
---|
1147 | buffer[i] = 0;
|
---|
1148 | }
|
---|
1149 | return buffer;
|
---|
1150 | };
|
---|
1151 |
|
---|
1152 | /**
|
---|
1153 | * Copies the content of a buffer to another buffer.
|
---|
1154 | *
|
---|
1155 | * @static Sink
|
---|
1156 | * @name clone
|
---|
1157 | *
|
---|
1158 | * @arg {Buffer} buffer The buffer to copy from.
|
---|
1159 | * @arg {Buffer} !result The buffer to copy to.
|
---|
1160 | *
|
---|
1161 | * @return {Buffer} A clone of the buffer.
|
---|
1162 | */
|
---|
1163 |
|
---|
1164 | Sink.clone = function (buffer, result) {
|
---|
1165 | var l = buffer.length,
|
---|
1166 | i;
|
---|
1167 | result = result || new Float32Array(l);
|
---|
1168 | for (i=0; i<l; i++){
|
---|
1169 | result[i] = buffer[i];
|
---|
1170 | }
|
---|
1171 | return result;
|
---|
1172 | };
|
---|
1173 |
|
---|
1174 | /**
|
---|
1175 | * Creates an array of buffers of the specified length and the specified count.
|
---|
1176 | *
|
---|
1177 | * @static Sink
|
---|
1178 | * @name createDeinterleaved
|
---|
1179 | *
|
---|
1180 | * @arg {Number} length The length of a single channel.
|
---|
1181 | * @arg {Number} channelCount The number of channels.
|
---|
1182 | * @return {Array} The array of buffers.
|
---|
1183 | */
|
---|
1184 |
|
---|
1185 | Sink.createDeinterleaved = function (length, channelCount) {
|
---|
1186 | var result = new Array(channelCount),
|
---|
1187 | i;
|
---|
1188 | for (i=0; i<channelCount; i++){
|
---|
1189 | result[i] = new Float32Array(length);
|
---|
1190 | }
|
---|
1191 | return result;
|
---|
1192 | };
|
---|
1193 |
|
---|
1194 | Sink.memcpy = function (src, srcOffset, dst, dstOffset, length) {
|
---|
1195 | src = src.subarray || src.slice ? src : src.buffer;
|
---|
1196 | dst = dst.subarray || dst.slice ? dst : dst.buffer;
|
---|
1197 |
|
---|
1198 | src = srcOffset ? src.subarray ?
|
---|
1199 | src.subarray(srcOffset, length && srcOffset + length) :
|
---|
1200 | src.slice(srcOffset, length && srcOffset + length) : src;
|
---|
1201 |
|
---|
1202 | if (dst.set) {
|
---|
1203 | dst.set(src, dstOffset);
|
---|
1204 | } else {
|
---|
1205 | for (var i=0; i<src.length; i++) {
|
---|
1206 | dst[i + dstOffset] = src[i];
|
---|
1207 | }
|
---|
1208 | }
|
---|
1209 |
|
---|
1210 | return dst;
|
---|
1211 | };
|
---|
1212 |
|
---|
1213 | Sink.memslice = function (buffer, offset, length) {
|
---|
1214 | return buffer.subarray ? buffer.subarray(offset, length) : buffer.slice(offset, length);
|
---|
1215 | };
|
---|
1216 |
|
---|
1217 | Sink.mempad = function (buffer, out, offset) {
|
---|
1218 | out = out.length ? out : new (buffer.constructor)(out);
|
---|
1219 | Sink.memcpy(buffer, 0, out, offset);
|
---|
1220 | return out;
|
---|
1221 | };
|
---|
1222 |
|
---|
1223 | Sink.linspace = function (start, end, out) {
|
---|
1224 | var l, i, n, step;
|
---|
1225 | out = out.length ? (l=out.length) && out : Array(l=out);
|
---|
1226 | step = (end - start) / --l;
|
---|
1227 | for (n=start+step, i=1; i<l; i++, n+=step) {
|
---|
1228 | out[i] = n;
|
---|
1229 | }
|
---|
1230 | out[0] = start;
|
---|
1231 | out[l] = end;
|
---|
1232 | return out;
|
---|
1233 | };
|
---|
1234 |
|
---|
1235 | Sink.ftoi = function (input, bitCount, output) {
|
---|
1236 | var i, mask = Math.pow(2, bitCount - 1);
|
---|
1237 |
|
---|
1238 | output = output || new (input.constructor)(input.length);
|
---|
1239 |
|
---|
1240 | for (i=0; i<input.length; i++) {
|
---|
1241 | output[i] = ~~(mask * input[i]);
|
---|
1242 | }
|
---|
1243 |
|
---|
1244 | return output;
|
---|
1245 | };
|
---|
1246 |
|
---|
1247 | }(this.Sink));
|
---|
1248 | (function (Sink) {
|
---|
1249 |
|
---|
1250 | function Proxy (bufferSize, channelCount) {
|
---|
1251 | Sink.EventEmitter.call(this);
|
---|
1252 |
|
---|
1253 | this.bufferSize = isNaN(bufferSize) || bufferSize === null ? this.bufferSize : bufferSize;
|
---|
1254 | this.channelCount = isNaN(channelCount) || channelCount === null ? this.channelCount : channelCount;
|
---|
1255 |
|
---|
1256 | var self = this;
|
---|
1257 | this.callback = function () {
|
---|
1258 | return self.process.apply(self, arguments);
|
---|
1259 | };
|
---|
1260 |
|
---|
1261 | this.resetBuffer();
|
---|
1262 | }
|
---|
1263 |
|
---|
1264 | Proxy.prototype = {
|
---|
1265 | buffer: null,
|
---|
1266 | zeroBuffer: null,
|
---|
1267 | parentSink: null,
|
---|
1268 | bufferSize: 4096,
|
---|
1269 | channelCount: 2,
|
---|
1270 | offset: null,
|
---|
1271 |
|
---|
1272 | resetBuffer: function () {
|
---|
1273 | this.buffer = new Float32Array(this.bufferSize);
|
---|
1274 | this.zeroBuffer = new Float32Array(this.bufferSize);
|
---|
1275 | },
|
---|
1276 |
|
---|
1277 | process: function (buffer, channelCount) {
|
---|
1278 | if (this.offset === null) {
|
---|
1279 | this.loadBuffer();
|
---|
1280 | }
|
---|
1281 |
|
---|
1282 | for (var i=0; i<buffer.length; i++) {
|
---|
1283 | if (this.offset >= this.buffer.length) {
|
---|
1284 | this.loadBuffer();
|
---|
1285 | }
|
---|
1286 |
|
---|
1287 | buffer[i] = this.buffer[this.offset++];
|
---|
1288 | }
|
---|
1289 | },
|
---|
1290 |
|
---|
1291 | loadBuffer: function () {
|
---|
1292 | this.offset = 0;
|
---|
1293 | Sink.memcpy(this.zeroBuffer, 0, this.buffer, 0);
|
---|
1294 | this.emit('audioprocess', [this.buffer, this.channelCount]);
|
---|
1295 | }
|
---|
1296 | };
|
---|
1297 |
|
---|
1298 | Sink.Proxy = Proxy;
|
---|
1299 |
|
---|
1300 | /**
|
---|
1301 | * Creates a proxy callback system for the sink instance.
|
---|
1302 | * Requires Sink utils.
|
---|
1303 | *
|
---|
1304 | * @method Sink
|
---|
1305 | * @method createProxy
|
---|
1306 | *
|
---|
1307 | * @arg {Number} !bufferSize The buffer size for the proxy.
|
---|
1308 | */
|
---|
1309 | Sink.prototype.createProxy = function (bufferSize) {
|
---|
1310 | var proxy = new Sink.Proxy(bufferSize, this.channelCount);
|
---|
1311 | proxy.parentSink = this;
|
---|
1312 |
|
---|
1313 | this.on('audioprocess', proxy.callback);
|
---|
1314 |
|
---|
1315 | return proxy;
|
---|
1316 | };
|
---|
1317 |
|
---|
1318 | }(this.Sink));
|
---|
1319 | (function (Sink) {
|
---|
1320 |
|
---|
1321 | (function(){
|
---|
1322 |
|
---|
1323 | /**
|
---|
1324 | * If method is supplied, adds a new interpolation method to Sink.interpolation, otherwise sets the default interpolation method (Sink.interpolate) to the specified property of Sink.interpolate.
|
---|
1325 | *
|
---|
1326 | * @arg {String} name The name of the interpolation method to get / set.
|
---|
1327 | * @arg {Function} !method The interpolation method.
|
---|
1328 | */
|
---|
1329 |
|
---|
1330 | function interpolation(name, method) {
|
---|
1331 | if (name && method) {
|
---|
1332 | interpolation[name] = method;
|
---|
1333 | } else if (name && interpolation[name] instanceof Function) {
|
---|
1334 | Sink.interpolate = interpolation[name];
|
---|
1335 | }
|
---|
1336 | return interpolation[name];
|
---|
1337 | }
|
---|
1338 |
|
---|
1339 | Sink.interpolation = interpolation;
|
---|
1340 |
|
---|
1341 |
|
---|
1342 | /**
|
---|
1343 | * Interpolates a fractal part position in an array to a sample. (Linear interpolation)
|
---|
1344 | *
|
---|
1345 | * @param {Array} arr The sample buffer.
|
---|
1346 | * @param {number} pos The position to interpolate from.
|
---|
1347 | * @return {Float32} The interpolated sample.
|
---|
1348 | */
|
---|
1349 | interpolation('linear', function (arr, pos) {
|
---|
1350 | var first = Math.floor(pos),
|
---|
1351 | second = first + 1,
|
---|
1352 | frac = pos - first;
|
---|
1353 | second = second < arr.length ? second : 0;
|
---|
1354 | return arr[first] * (1 - frac) + arr[second] * frac;
|
---|
1355 | });
|
---|
1356 |
|
---|
1357 | /**
|
---|
1358 | * Interpolates a fractal part position in an array to a sample. (Nearest neighbour interpolation)
|
---|
1359 | *
|
---|
1360 | * @param {Array} arr The sample buffer.
|
---|
1361 | * @param {number} pos The position to interpolate from.
|
---|
1362 | * @return {Float32} The interpolated sample.
|
---|
1363 | */
|
---|
1364 | interpolation('nearest', function (arr, pos) {
|
---|
1365 | return pos >= arr.length - 0.5 ? arr[0] : arr[Math.round(pos)];
|
---|
1366 | });
|
---|
1367 |
|
---|
1368 | interpolation('linear');
|
---|
1369 |
|
---|
1370 | }());
|
---|
1371 |
|
---|
1372 |
|
---|
1373 | /**
|
---|
1374 | * Resamples a sample buffer from a frequency to a frequency and / or from a sample rate to a sample rate.
|
---|
1375 | *
|
---|
1376 | * @static Sink
|
---|
1377 | * @name resample
|
---|
1378 | *
|
---|
1379 | * @arg {Buffer} buffer The sample buffer to resample.
|
---|
1380 | * @arg {Number} fromRate The original sample rate of the buffer, or if the last argument, the speed ratio to convert with.
|
---|
1381 | * @arg {Number} fromFrequency The original frequency of the buffer, or if the last argument, used as toRate and the secondary comparison will not be made.
|
---|
1382 | * @arg {Number} toRate The sample rate of the created buffer.
|
---|
1383 | * @arg {Number} toFrequency The frequency of the created buffer.
|
---|
1384 | *
|
---|
1385 | * @return The new resampled buffer.
|
---|
1386 | */
|
---|
1387 | Sink.resample = function (buffer, fromRate /* or speed */, fromFrequency /* or toRate */, toRate, toFrequency) {
|
---|
1388 | var
|
---|
1389 | argc = arguments.length,
|
---|
1390 | speed = argc === 2 ? fromRate : argc === 3 ? fromRate / fromFrequency : toRate / fromRate * toFrequency / fromFrequency,
|
---|
1391 | l = buffer.length,
|
---|
1392 | length = Math.ceil(l / speed),
|
---|
1393 | newBuffer = new Float32Array(length),
|
---|
1394 | i, n;
|
---|
1395 | for (i=0, n=0; i<l; i += speed) {
|
---|
1396 | newBuffer[n++] = Sink.interpolate(buffer, i);
|
---|
1397 | }
|
---|
1398 | return newBuffer;
|
---|
1399 | };
|
---|
1400 |
|
---|
1401 | }(this.Sink));
|
---|
1402 | void function (Sink) {
|
---|
1403 |
|
---|
1404 | Sink.on('init', function (sink) {
|
---|
1405 | sink.activeRecordings = [];
|
---|
1406 | sink.on('postprocess', sink.recordData);
|
---|
1407 | });
|
---|
1408 |
|
---|
1409 | Sink.prototype.activeRecordings = null;
|
---|
1410 |
|
---|
1411 | /**
|
---|
1412 | * Starts recording the sink output.
|
---|
1413 | *
|
---|
1414 | * @method Sink
|
---|
1415 | * @name record
|
---|
1416 | *
|
---|
1417 | * @return {Recording} The recording object for the recording started.
|
---|
1418 | */
|
---|
1419 | Sink.prototype.record = function () {
|
---|
1420 | var recording = new Sink.Recording(this);
|
---|
1421 | this.emit('record', [recording]);
|
---|
1422 | return recording;
|
---|
1423 | };
|
---|
1424 | /**
|
---|
1425 | * Private method that handles the adding the buffers to all the current recordings.
|
---|
1426 | *
|
---|
1427 | * @method Sink
|
---|
1428 | * @method recordData
|
---|
1429 | *
|
---|
1430 | * @arg {Array} buffer The buffer to record.
|
---|
1431 | */
|
---|
1432 | Sink.prototype.recordData = function (buffer) {
|
---|
1433 | var activeRecs = this.activeRecordings,
|
---|
1434 | i, l = activeRecs.length;
|
---|
1435 | for (i=0; i<l; i++) {
|
---|
1436 | activeRecs[i].add(buffer);
|
---|
1437 | }
|
---|
1438 | };
|
---|
1439 |
|
---|
1440 | /**
|
---|
1441 | * A Recording class for recording sink output.
|
---|
1442 | *
|
---|
1443 | * @class
|
---|
1444 | * @static Sink
|
---|
1445 | * @arg {Object} bindTo The sink to bind the recording to.
|
---|
1446 | */
|
---|
1447 |
|
---|
1448 | function Recording (bindTo) {
|
---|
1449 | this.boundTo = bindTo;
|
---|
1450 | this.buffers = [];
|
---|
1451 | bindTo.activeRecordings.push(this);
|
---|
1452 | }
|
---|
1453 |
|
---|
1454 | Recording.prototype = {
|
---|
1455 | /**
|
---|
1456 | * Adds a new buffer to the recording.
|
---|
1457 | *
|
---|
1458 | * @arg {Array} buffer The buffer to add.
|
---|
1459 | *
|
---|
1460 | * @method Recording
|
---|
1461 | */
|
---|
1462 | add: function (buffer) {
|
---|
1463 | this.buffers.push(buffer);
|
---|
1464 | },
|
---|
1465 | /**
|
---|
1466 | * Empties the recording.
|
---|
1467 | *
|
---|
1468 | * @method Recording
|
---|
1469 | */
|
---|
1470 | clear: function () {
|
---|
1471 | this.buffers = [];
|
---|
1472 | },
|
---|
1473 | /**
|
---|
1474 | * Stops the recording and unbinds it from it's host sink.
|
---|
1475 | *
|
---|
1476 | * @method Recording
|
---|
1477 | */
|
---|
1478 | stop: function () {
|
---|
1479 | var recordings = this.boundTo.activeRecordings,
|
---|
1480 | i;
|
---|
1481 | for (i=0; i<recordings.length; i++) {
|
---|
1482 | if (recordings[i] === this) {
|
---|
1483 | recordings.splice(i--, 1);
|
---|
1484 | }
|
---|
1485 | }
|
---|
1486 | },
|
---|
1487 | /**
|
---|
1488 | * Joins the recorded buffers into a single buffer.
|
---|
1489 | *
|
---|
1490 | * @method Recording
|
---|
1491 | */
|
---|
1492 | join: function () {
|
---|
1493 | var bufferLength = 0,
|
---|
1494 | bufPos = 0,
|
---|
1495 | buffers = this.buffers,
|
---|
1496 | newArray,
|
---|
1497 | n, i, l = buffers.length;
|
---|
1498 |
|
---|
1499 | for (i=0; i<l; i++) {
|
---|
1500 | bufferLength += buffers[i].length;
|
---|
1501 | }
|
---|
1502 | newArray = new Float32Array(bufferLength);
|
---|
1503 | for (i=0; i<l; i++) {
|
---|
1504 | for (n=0; n<buffers[i].length; n++) {
|
---|
1505 | newArray[bufPos + n] = buffers[i][n];
|
---|
1506 | }
|
---|
1507 | bufPos += buffers[i].length;
|
---|
1508 | }
|
---|
1509 | return newArray;
|
---|
1510 | }
|
---|
1511 | };
|
---|
1512 |
|
---|
1513 | Sink.Recording = Recording;
|
---|
1514 |
|
---|
1515 | }(this.Sink);
|
---|
1516 | void function (Sink) {
|
---|
1517 |
|
---|
1518 | function processRingBuffer () {
|
---|
1519 | if (this.ringBuffer) {
|
---|
1520 | (this.channelMode === 'interleaved' ? this.ringSpin : this.ringSpinInterleaved).apply(this, arguments);
|
---|
1521 | }
|
---|
1522 | }
|
---|
1523 |
|
---|
1524 | Sink.on('init', function (sink) {
|
---|
1525 | sink.on('preprocess', processRingBuffer);
|
---|
1526 | });
|
---|
1527 |
|
---|
1528 | Sink.prototype.ringBuffer = null;
|
---|
1529 |
|
---|
1530 | /**
|
---|
1531 | * A private method that applies the ring buffer contents to the specified buffer, while in interleaved mode.
|
---|
1532 | *
|
---|
1533 | * @method Sink
|
---|
1534 | * @name ringSpin
|
---|
1535 | *
|
---|
1536 | * @arg {Array} buffer The buffer to write to.
|
---|
1537 | */
|
---|
1538 | Sink.prototype.ringSpin = function (buffer) {
|
---|
1539 | var ring = this.ringBuffer,
|
---|
1540 | l = buffer.length,
|
---|
1541 | m = ring.length,
|
---|
1542 | off = this.ringOffset,
|
---|
1543 | i;
|
---|
1544 | for (i=0; i<l; i++){
|
---|
1545 | buffer[i] += ring[off];
|
---|
1546 | off = (off + 1) % m;
|
---|
1547 | }
|
---|
1548 | this.ringOffset = off;
|
---|
1549 | };
|
---|
1550 |
|
---|
1551 | /**
|
---|
1552 | * A private method that applies the ring buffer contents to the specified buffer, while in deinterleaved mode.
|
---|
1553 | *
|
---|
1554 | * @method Sink
|
---|
1555 | * @name ringSpinDeinterleaved
|
---|
1556 | *
|
---|
1557 | * @param {Array} buffer The buffers to write to.
|
---|
1558 | */
|
---|
1559 | Sink.prototype.ringSpinDeinterleaved = function (buffer) {
|
---|
1560 | var ring = this.ringBuffer,
|
---|
1561 | l = buffer.length,
|
---|
1562 | ch = ring.length,
|
---|
1563 | m = ring[0].length,
|
---|
1564 | len = ch * m,
|
---|
1565 | off = this.ringOffset,
|
---|
1566 | i, n;
|
---|
1567 | for (i=0; i<l; i+=ch){
|
---|
1568 | for (n=0; n<ch; n++){
|
---|
1569 | buffer[i + n] += ring[n][off];
|
---|
1570 | }
|
---|
1571 | off = (off + 1) % m;
|
---|
1572 | }
|
---|
1573 | this.ringOffset = n;
|
---|
1574 | };
|
---|
1575 |
|
---|
1576 | }(this.Sink);
|
---|
1577 | void function (Sink, proto) {
|
---|
1578 |
|
---|
1579 | proto = Sink.prototype;
|
---|
1580 |
|
---|
1581 | Sink.on('init', function (sink) {
|
---|
1582 | sink.asyncBuffers = [];
|
---|
1583 | sink.syncBuffers = [];
|
---|
1584 | sink.on('preprocess', sink.writeBuffersSync);
|
---|
1585 | sink.on('postprocess', sink.writeBuffersAsync);
|
---|
1586 | });
|
---|
1587 |
|
---|
1588 | proto.writeMode = 'async';
|
---|
1589 | proto.asyncBuffers = proto.syncBuffers = null;
|
---|
1590 |
|
---|
1591 | /**
|
---|
1592 | * Private method that handles the mixing of asynchronously written buffers.
|
---|
1593 | *
|
---|
1594 | * @method Sink
|
---|
1595 | * @name writeBuffersAsync
|
---|
1596 | *
|
---|
1597 | * @arg {Array} buffer The buffer to write to.
|
---|
1598 | */
|
---|
1599 | proto.writeBuffersAsync = function (buffer) {
|
---|
1600 | var buffers = this.asyncBuffers,
|
---|
1601 | l = buffer.length,
|
---|
1602 | buf,
|
---|
1603 | bufLength,
|
---|
1604 | i, n, offset;
|
---|
1605 | if (buffers) {
|
---|
1606 | for (i=0; i<buffers.length; i++) {
|
---|
1607 | buf = buffers[i];
|
---|
1608 | bufLength = buf.b.length;
|
---|
1609 | offset = buf.d;
|
---|
1610 | buf.d -= Math.min(offset, l);
|
---|
1611 |
|
---|
1612 | for (n=0; n + offset < l && n < bufLength; n++) {
|
---|
1613 | buffer[n + offset] += buf.b[n];
|
---|
1614 | }
|
---|
1615 | buf.b = buf.b.subarray(n + offset);
|
---|
1616 | if (i >= bufLength) {
|
---|
1617 | buffers.splice(i--, 1);
|
---|
1618 | }
|
---|
1619 | }
|
---|
1620 | }
|
---|
1621 | };
|
---|
1622 |
|
---|
1623 | /**
|
---|
1624 | * A private method that handles mixing synchronously written buffers.
|
---|
1625 | *
|
---|
1626 | * @method Sink
|
---|
1627 | * @name writeBuffersSync
|
---|
1628 | *
|
---|
1629 | * @arg {Array} buffer The buffer to write to.
|
---|
1630 | */
|
---|
1631 | proto.writeBuffersSync = function (buffer) {
|
---|
1632 | var buffers = this.syncBuffers,
|
---|
1633 | l = buffer.length,
|
---|
1634 | i = 0,
|
---|
1635 | soff = 0;
|
---|
1636 | for (;i<l && buffers.length; i++) {
|
---|
1637 | buffer[i] += buffers[0][soff];
|
---|
1638 | if (buffers[0].length <= soff){
|
---|
1639 | buffers.splice(0, 1);
|
---|
1640 | soff = 0;
|
---|
1641 | continue;
|
---|
1642 | }
|
---|
1643 | soff++;
|
---|
1644 | }
|
---|
1645 | if (buffers.length) {
|
---|
1646 | buffers[0] = buffers[0].subarray(soff);
|
---|
1647 | }
|
---|
1648 | };
|
---|
1649 |
|
---|
1650 | /**
|
---|
1651 | * Writes a buffer asynchronously on top of the existing signal, after a specified delay.
|
---|
1652 | *
|
---|
1653 | * @method Sink
|
---|
1654 | * @name writeBufferAsync
|
---|
1655 | *
|
---|
1656 | * @arg {Array} buffer The buffer to write.
|
---|
1657 | * @arg {Number} delay The delay to write after. If not specified, the Sink will calculate a delay to compensate the latency.
|
---|
1658 | * @return {Number} The number of currently stored asynchronous buffers.
|
---|
1659 | */
|
---|
1660 | proto.writeBufferAsync = function (buffer, delay) {
|
---|
1661 | buffer = this.mode === 'deinterleaved' ? Sink.interleave(buffer, this.channelCount) : buffer;
|
---|
1662 | var buffers = this.asyncBuffers;
|
---|
1663 | buffers.push({
|
---|
1664 | b: buffer,
|
---|
1665 | d: isNaN(delay) ? ~~((+new Date() - this.previousHit) / 1000 * this.sampleRate) : delay
|
---|
1666 | });
|
---|
1667 | return buffers.length;
|
---|
1668 | };
|
---|
1669 |
|
---|
1670 | /**
|
---|
1671 | * Writes a buffer synchronously to the output.
|
---|
1672 | *
|
---|
1673 | * @method Sink
|
---|
1674 | * @name writeBufferSync
|
---|
1675 | *
|
---|
1676 | * @param {Array} buffer The buffer to write.
|
---|
1677 | * @return {Number} The number of currently stored synchronous buffers.
|
---|
1678 | */
|
---|
1679 | proto.writeBufferSync = function (buffer) {
|
---|
1680 | buffer = this.mode === 'deinterleaved' ? Sink.interleave(buffer, this.channelCount) : buffer;
|
---|
1681 | var buffers = this.syncBuffers;
|
---|
1682 | buffers.push(buffer);
|
---|
1683 | return buffers.length;
|
---|
1684 | };
|
---|
1685 |
|
---|
1686 | /**
|
---|
1687 | * Writes a buffer, according to the write mode specified.
|
---|
1688 | *
|
---|
1689 | * @method Sink
|
---|
1690 | * @name writeBuffer
|
---|
1691 | *
|
---|
1692 | * @arg {Array} buffer The buffer to write.
|
---|
1693 | * @arg {Number} delay The delay to write after. If not specified, the Sink will calculate a delay to compensate the latency. (only applicable in asynchronous write mode)
|
---|
1694 | * @return {Number} The number of currently stored (a)synchronous buffers.
|
---|
1695 | */
|
---|
1696 | proto.writeBuffer = function () {
|
---|
1697 | return this[this.writeMode === 'async' ? 'writeBufferAsync' : 'writeBufferSync'].apply(this, arguments);
|
---|
1698 | };
|
---|
1699 |
|
---|
1700 | /**
|
---|
1701 | * Gets the total amount of yet unwritten samples in the synchronous buffers.
|
---|
1702 | *
|
---|
1703 | * @method Sink
|
---|
1704 | * @name getSyncWriteOffset
|
---|
1705 | *
|
---|
1706 | * @return {Number} The total amount of yet unwritten samples in the synchronous buffers.
|
---|
1707 | */
|
---|
1708 | proto.getSyncWriteOffset = function () {
|
---|
1709 | var buffers = this.syncBuffers,
|
---|
1710 | offset = 0,
|
---|
1711 | i;
|
---|
1712 | for (i=0; i<buffers.length; i++) {
|
---|
1713 | offset += buffers[i].length;
|
---|
1714 | }
|
---|
1715 | return offset;
|
---|
1716 | };
|
---|
1717 |
|
---|
1718 | } (this.Sink);
|
---|