source: gs3-extensions/web-audio/trunk/js-mad/script/sink.js@ 28388

Last change on this file since 28388 was 28388, checked in by davidb, 11 years ago

Set of JS, CSS, PNG etc web resources to support a mixture of audio player/document display capabilities

File size: 39.7 KB
Line 
1var 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*/
24function 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
38function SinkClass () {
39}
40
41Sink.SinkClass = SinkClass;
42
43SinkClass.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
135function 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
152Sink.sinks = Sink.devices = sinks;
153Sink.sinks.list = [];
154
155Sink.singleton = function () {
156 var sink = Sink.apply(null, arguments);
157
158 Sink.singleton = function () {
159 return sink;
160 };
161
162 return sink;
163};
164
165global.Sink = Sink;
166
167return Sink;
168
169}(function (){ return this; }());
170void function (Sink) {
171
172/**
173 * A light event emitter.
174 *
175 * @class
176 * @static Sink
177*/
178function 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
188EventEmitter.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
248Sink.EventEmitter = EventEmitter;
249
250EventEmitter.call(Sink);
251
252}(this.Sink);
253void 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
268Sink.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);
310void function (Sink) {
311
312var _Blob, _BlobBuilder, _URL, _btoa;
313
314void 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
341var createBlob = _Blob && _URL && function (content, type) {
342 return _URL.createObjectURL(new _Blob([content], { type: type }));
343};
344
345var 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
352var createData = _btoa && function (content, type) {
353 return 'data:' + type + ';base64,' + _btoa(content);
354};
355
356var createDynURL =
357 createBlob ||
358 createBlobBuilder ||
359 createData;
360
361if (!createDynURL) return;
362
363if (createBlob) createDynURL.createBlob = createBlob;
364if (createBlobBuilder) createDynURL.createBlobBuilder = createBlobBuilder;
365if (createData) createDynURL.createData = createData;
366
367if (_Blob) createDynURL.Blob = _Blob;
368if (_BlobBuilder) createDynURL.BlobBuilder = _BlobBuilder;
369if (_URL) createDynURL.URL = _URL;
370
371Sink.createDynURL = createDynURL;
372
373Sink.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);
382void 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
398function 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
412SinkError.prototype = new Error();
413
414SinkError.prototype.toString = function () {
415 return 'SinkError 0x' + this.code.toString(16) + ': ' + this.message;
416};
417
418SinkError[0x01] = {
419 message: 'No such error code.',
420 explanation: 'The error code does not exist.'
421};
422SinkError[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
427SinkError[0x10] = {
428 message: 'Buffer underflow.',
429 explanation: 'Trying to recover...'
430};
431SinkError[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};
435SinkError[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
440Sink.Error = SinkError;
441
442}(this.Sink);
443void 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
455var 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
465function 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
475function 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
517Sink.EventEmitter.call(inlineWorker);
518
519inlineWorker.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
559Sink.inlineWorker = inlineWorker;
560
561inlineWorker.test();
562
563}(this.Sink);
564void function (Sink) {
565
566/**
567 * A Sink class for the Mozilla Audio Data API.
568*/
569
570Sink.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
657Sink.sinks.moz = Sink.sinks.audiodata;
658
659}(this.Sink);
660void function (Sink) {
661
662var cubeb;
663
664try {
665 cubeb = require('cubeb');
666} catch (e) {
667 return;
668}
669
670var 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
684var streamCount = 0;
685
686Sink.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);
731void function (Sink) {
732
733/**
734 * A dummy Sink. (No output)
735*/
736
737Sink.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
759sinks = Sink.sinks;
760
761function 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*/
776sinks('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
819function 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
831wavAudio.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
854sinks.wav.wavAudio = wavAudio;
855
856}(this.Sink));
857 (function (sinks, fixChrome82795) {
858
859var AudioContext = typeof window === 'undefined' ? null : window.webkitAudioContext || window.AudioContext;
860
861/**
862 * A sink class for the Web Audio API
863*/
864
865sinks('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
931sinks.webkit = sinks.webaudio;
932
933sinks.webaudio.fix82795 = fixChrome82795;
934
935sinks.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
953Sink.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
1067Sink.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
1094Sink.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
1120Sink.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
1143Sink.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
1164Sink.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
1185Sink.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
1194Sink.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
1213Sink.memslice = function (buffer, offset, length) {
1214 return buffer.subarray ? buffer.subarray(offset, length) : buffer.slice(offset, length);
1215};
1216
1217Sink.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
1223Sink.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
1235Sink.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
1250function 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
1264Proxy.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
1298Sink.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*/
1309Sink.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
1330function 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
1339Sink.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*/
1349interpolation('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*/
1364interpolation('nearest', function (arr, pos) {
1365 return pos >= arr.length - 0.5 ? arr[0] : arr[Math.round(pos)];
1366});
1367
1368interpolation('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*/
1387Sink.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));
1402void function (Sink) {
1403
1404Sink.on('init', function (sink) {
1405 sink.activeRecordings = [];
1406 sink.on('postprocess', sink.recordData);
1407});
1408
1409Sink.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*/
1419Sink.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*/
1432Sink.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
1448function Recording (bindTo) {
1449 this.boundTo = bindTo;
1450 this.buffers = [];
1451 bindTo.activeRecordings.push(this);
1452}
1453
1454Recording.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
1513Sink.Recording = Recording;
1514
1515}(this.Sink);
1516void function (Sink) {
1517
1518function processRingBuffer () {
1519 if (this.ringBuffer) {
1520 (this.channelMode === 'interleaved' ? this.ringSpin : this.ringSpinInterleaved).apply(this, arguments);
1521 }
1522}
1523
1524Sink.on('init', function (sink) {
1525 sink.on('preprocess', processRingBuffer);
1526});
1527
1528Sink.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*/
1538Sink.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*/
1559Sink.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);
1577void function (Sink, proto) {
1578
1579proto = Sink.prototype;
1580
1581Sink.on('init', function (sink) {
1582 sink.asyncBuffers = [];
1583 sink.syncBuffers = [];
1584 sink.on('preprocess', sink.writeBuffersSync);
1585 sink.on('postprocess', sink.writeBuffersAsync);
1586});
1587
1588proto.writeMode = 'async';
1589proto.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*/
1599proto.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*/
1631proto.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*/
1660proto.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*/
1679proto.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*/
1696proto.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*/
1708proto.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);
Note: See TracBrowser for help on using the repository browser.