source: main/trunk/model-sites-dev/respooled/collect/popup-video-respooled/js/midi/inc/tuna/tuna.js@ 29863

Last change on this file since 29863 was 29863, checked in by davidb, 9 years ago

First cut at respooled site/collection

File size: 70.4 KB
Line 
1/*
2 Copyright (c) 2012 DinahMoe AB
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
5 files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
6 modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
7 is furnished to do so, subject to the following conditions:
8
9 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
11 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
13 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
14 OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
16 Bitcrusher & Moog Filter by Zach Denton
17*/
18
19// https://developer.mozilla.org/en-US/docs/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
20
21// Originally written by Alessandro Saccoia, Chris Coniglio and Oskar Eriksson
22
23(function (window) {
24 var userContext;
25 var userInstance;
26 var Tuna = function (context) {
27 if (!window.AudioContext) {
28 window.AudioContext = window.webkitAudioContext;
29 }
30 if (!context) {
31 console.log("tuna.js: Missing audio context! Creating a new context for you.");
32 context = window.AudioContext && (new window.AudioContext());
33 }
34 userContext = context;
35 userInstance = this;
36 },
37 version = "0.1",
38 set = "setValueAtTime",
39 linear = "linearRampToValueAtTime",
40 pipe = function (param, val) {
41 param.value = val;
42 },
43 Super = Object.create(null, {
44 activate: {
45 writable: true,
46 value: function (doActivate) {
47 if (doActivate) {
48 this.input.disconnect();
49 this.input.connect(this.activateNode);
50 if (this.activateCallback) {
51 this.activateCallback(doActivate);
52 }
53 } else {
54 this.input.disconnect();
55 this.input.connect(this.output);
56 }
57 }
58 },
59 bypass: {
60 get: function () {
61 return this._bypass;
62 },
63 set: function (value) {
64 if (this._lastBypassValue === value) {
65 return;
66 }
67 this._bypass = value;
68 this.activate(!value);
69 this._lastBypassValue = value;
70 }
71 },
72 connect: {
73 value: function (target) {
74 this.output.connect(target);
75 }
76 },
77 disconnect: {
78 value: function (target) {
79 this.output.disconnect(target);
80 }
81 },
82 connectInOrder: {
83 value: function (nodeArray) {
84 var i = nodeArray.length - 1;
85 while(i--) {
86 if (!nodeArray[i].connect) {
87 return console.error("AudioNode.connectInOrder: TypeError: Not an AudioNode.", nodeArray[i]);
88 }
89 if (nodeArray[i + 1].input) {
90 nodeArray[i].connect(nodeArray[i + 1].input);
91 } else {
92 nodeArray[i].connect(nodeArray[i + 1]);
93 }
94 }
95 }
96 },
97 getDefaults: {
98 value: function () {
99 var result = {};
100 for(var key in this.defaults) {
101 result[key] = this.defaults[key].value;
102 }
103 return result;
104 }
105 },
106 getValues: {
107 value: function () {
108 var result = {};
109 for(var key in this.defaults) {
110 result[key] = this[key];
111 }
112 return result;
113 }
114 },
115 automate: {
116 value: function (property, value, duration, startTime) {
117 var start = startTime ? ~~ (startTime / 1000) : userContext.currentTime,
118 dur = duration ? ~~ (duration / 1000) : 0,
119 _is = this.defaults[property],
120 param = this[property],
121 method;
122
123 if (param) {
124 if (_is.automatable) {
125 if (!duration) {
126 method = set;
127 } else {
128 method = linear;
129 param.cancelScheduledValues(start);
130 param.setValueAtTime(param.value, start);
131 }
132 param[method](value, dur + start);
133 } else {
134 param = value;
135 }
136 } else {
137 console.error("Invalid Property for " + this.name);
138 }
139 }
140 }
141 }),
142 FLOAT = "float",
143 BOOLEAN = "boolean",
144 STRING = "string",
145 INT = "int";
146
147 function dbToWAVolume(db) {
148 return Math.max(0, Math.round(100 * Math.pow(2, db / 6)) / 100);
149 }
150
151 function fmod(x, y) {
152 // http://kevin.vanzonneveld.net
153 // + original by: Onno Marsman
154 // + input by: Brett Zamir (http://brett-zamir.me)
155 // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
156 // * example 1: fmod(5.7, 1.3);
157 // * returns 1: 0.5
158 var tmp, tmp2, p = 0,
159 pY = 0,
160 l = 0.0,
161 l2 = 0.0;
162
163 tmp = x.toExponential().match(/^.\.?(.*)e(.+)$/);
164 p = parseInt(tmp[2], 10) - (tmp[1] + '').length;
165 tmp = y.toExponential().match(/^.\.?(.*)e(.+)$/);
166 pY = parseInt(tmp[2], 10) - (tmp[1] + '').length;
167
168 if (pY > p) {
169 p = pY;
170 }
171
172 tmp2 = (x % y);
173
174 if (p < -100 || p > 20) {
175 // toFixed will give an out of bound error so we fix it like this:
176 l = Math.round(Math.log(tmp2) / Math.log(10));
177 l2 = Math.pow(10, l);
178
179 return(tmp2 / l2).toFixed(l - p) * l2;
180 } else {
181 return parseFloat(tmp2.toFixed(-p));
182 }
183 }
184
185 function sign(x) {
186 if (x === 0) {
187 return 1;
188 } else {
189 return Math.abs(x) / x;
190 }
191 }
192
193 function tanh(n) {
194 return(Math.exp(n) - Math.exp(-n)) / (Math.exp(n) + Math.exp(-n));
195 }
196
197 Tuna.toString = Tuna.prototype.toString = function () {
198 return "You are running Tuna version " + version + " by Dinahmoe!";
199 };
200 Tuna.prototype.Filter = function (properties) {
201 if (!properties) {
202 properties = this.getDefaults();
203 }
204 this.input = userContext.createGain();
205 this.activateNode = userContext.createGain();
206 this.filter = userContext.createBiquadFilter();
207 this.output = userContext.createGain();
208
209 this.activateNode.connect(this.filter);
210 this.filter.connect(this.output);
211
212 this.frequency = properties.frequency || this.defaults.frequency.value;
213 this.Q = properties.resonance || this.defaults.Q.value;
214 this.filterType = properties.filterType || this.defaults.filterType.value;
215 this.gain = properties.gain || this.defaults.gain.value;
216 this.bypass = properties.bypass || false;
217 };
218 Tuna.prototype.Filter.prototype = Object.create(Super, {
219 name: {
220 value: "Filter"
221 },
222 defaults: {
223 writable:true,
224 value: {
225 frequency: {
226 value: 800,
227 min: 20,
228 max: 22050,
229 automatable: true
230 },
231 Q: {
232 value: 1,
233 min: 0.001,
234 max: 100,
235 automatable: true
236 },
237 gain: {
238 value: 0,
239 min: -40,
240 max: 40,
241 automatable: true
242 },
243 bypass: {
244 value: true,
245 automatable: false,
246 type: BOOLEAN
247 },
248 filterType: {
249 value: 1,
250 min: 0,
251 max: 7,
252 automatable: false,
253 type: INT
254 }
255 }
256 },
257 filterType: {
258 enumerable: true,
259 get: function () {
260 return this.filter.type;
261 },
262 set: function (value) {
263 this.filter.type = value;
264 }
265 },
266 Q: {
267 enumerable: true,
268 get: function () {
269 return this.filter.Q;
270 },
271 set: function (value) {
272 this.filter.Q.value = value;
273 }
274 },
275 gain: {
276 enumerable: true,
277 get: function () {
278 return this.filter.gain;
279 },
280 set: function (value) {
281 this.filter.gain.value = value;
282 }
283 },
284 frequency: {
285 enumerable: true,
286 get: function () {
287 return this.filter.frequency;
288 },
289 set: function (value) {
290 this.filter.frequency.value = value;
291 }
292 }
293 });
294 Tuna.prototype.Bitcrusher = function (properties) {
295 if (!properties) {
296 properties = this.getDefaults();
297 }
298 this.bufferSize = properties.bufferSize || this.defaults.bufferSize.value;
299
300 this.input = userContext.createGain();
301 this.activateNode = userContext.createGain();
302 this.processor = userContext.createScriptProcessor(this.bufferSize, 1, 1);
303 this.output = userContext.createGain();
304
305 this.activateNode.connect(this.processor);
306 this.processor.connect(this.output);
307
308 var phaser = 0, last = 0;
309 this.processor.onaudioprocess = function (e) {
310 var input = e.inputBuffer.getChannelData(0),
311 output = e.outputBuffer.getChannelData(0),
312 step = Math.pow(1/2, this.bits);
313 for(var i = 0; i < input.length; i++) {
314 phaser += this.normfreq;
315 if (phaser >= 1.0) {
316 phaser -= 1.0;
317 last = step * Math.floor(input[i] / step + 0.5);
318 }
319 output[i] = last;
320 }
321 };
322
323 this.bits = properties.bits || this.defaults.bits.value;
324 this.normfreq = properties.normfreq || this.defaults.normfreq.value;
325 this.bypass = properties.bypass || false;
326 };
327 Tuna.prototype.Bitcrusher.prototype = Object.create(Super, {
328 name: {
329 value: "Bitcrusher"
330 },
331 defaults: {
332 writable: true,
333 value: {
334 bits: {
335 value: 4,
336 min: 1,
337 max: 16,
338 automatable: false,
339 type: INT
340 },
341 bufferSize: {
342 value: 4096,
343 min: 256,
344 max: 16384,
345 automatable: false,
346 type: INT
347 },
348 bypass: {
349 value: false,
350 automatable: false,
351 type: BOOLEAN
352 },
353 normfreq: {
354 value: 0.1,
355 min: 0.0001,
356 max: 1.0,
357 automatable: false,
358 }
359 }
360 },
361 bits: {
362 enumerable: true,
363 get: function () {
364 return this.processor.bits;
365 },
366 set: function (value) {
367 this.processor.bits = value;
368 }
369 },
370 normfreq: {
371 enumerable: true,
372 get: function () {
373 return this.processor.normfreq;
374 },
375 set: function (value) {
376 this.processor.normfreq = value;
377 }
378 }
379 });
380 Tuna.prototype.Cabinet = function (properties) {
381 if (!properties) {
382 properties = this.getDefaults();
383 }
384 this.input = userContext.createGain();
385 this.activateNode = userContext.createGain();
386 this.convolver = this.newConvolver(properties.impulsePath || "../impulses/impulse_guitar.wav");
387 this.makeupNode = userContext.createGain();
388 this.output = userContext.createGain();
389
390 this.activateNode.connect(this.convolver.input);
391 this.convolver.output.connect(this.makeupNode);
392 this.makeupNode.connect(this.output);
393
394 this.makeupGain = properties.makeupGain || this.defaults.makeupGain;
395 this.bypass = properties.bypass || false;
396 };
397 Tuna.prototype.Cabinet.prototype = Object.create(Super, {
398 name: {
399 value: "Cabinet"
400 },
401 defaults: {
402 writable:true,
403 value: {
404 makeupGain: {
405 value: 1,
406 min: 0,
407 max: 20,
408 automatable: true
409 },
410 bypass: {
411 value: false,
412 automatable: false,
413 type: BOOLEAN
414 }
415 }
416 },
417 makeupGain: {
418 enumerable: true,
419 get: function () {
420 return this.makeupNode.gain;
421 },
422 set: function (value) {
423 this.makeupNode.gain.value = value;
424 }
425 },
426 newConvolver: {
427 value: function (impulsePath) {
428 return new userInstance.Convolver({
429 impulse: impulsePath,
430 dryLevel: 0,
431 wetLevel: 1
432 });
433 }
434 }
435 });
436 Tuna.prototype.Chorus = function (properties) {
437 if (!properties) {
438 properties = this.getDefaults();
439 }
440 this.input = userContext.createGain();
441 this.attenuator = this.activateNode = userContext.createGain();
442 this.splitter = userContext.createChannelSplitter(2);
443 this.delayL = userContext.createDelayNode();
444 this.delayR = userContext.createDelayNode();
445 this.feedbackGainNodeLR = userContext.createGain();
446 this.feedbackGainNodeRL = userContext.createGain();
447 this.merger = userContext.createChannelMerger(2);
448 this.output = userContext.createGain();
449
450 this.lfoL = new userInstance.LFO({
451 target: this.delayL.delayTime,
452 callback: pipe
453 });
454 this.lfoR = new userInstance.LFO({
455 target: this.delayR.delayTime,
456 callback: pipe
457 });
458
459 this.input.connect(this.attenuator);
460 this.attenuator.connect(this.output);
461 this.attenuator.connect(this.splitter);
462 this.splitter.connect(this.delayL, 0);
463 this.splitter.connect(this.delayR, 1);
464 this.delayL.connect(this.feedbackGainNodeLR);
465 this.delayR.connect(this.feedbackGainNodeRL);
466 this.feedbackGainNodeLR.connect(this.delayR);
467 this.feedbackGainNodeRL.connect(this.delayL);
468 this.delayL.connect(this.merger, 0, 0);
469 this.delayR.connect(this.merger, 0, 1);
470 this.merger.connect(this.output);
471
472 this.feedback = properties.feedback || this.defaults.feedback.value;
473 this.rate = properties.rate || this.defaults.rate.value;
474 this.delay = properties.delay || this.defaults.delay.value;
475 this.depth = properties.depth || this.defaults.depth.value;
476 this.lfoR.phase = Math.PI / 2;
477 this.attenuator.gain.value = 0.6934; // 1 / (10 ^ (((20 * log10(3)) / 3) / 20))
478 this.lfoL.activate(true);
479 this.lfoR.activate(true);
480 this.bypass = properties.bypass || false;
481 };
482 Tuna.prototype.Chorus.prototype = Object.create(Super, {
483 name: {
484 value: "Chorus"
485 },
486 defaults: {
487 writable:true,
488 value: {
489 feedback: {
490 value: 0.4,
491 min: 0,
492 max: 0.95,
493 automatable: false,
494 },
495 delay: {
496 value: 0.0045,
497 min: 0,
498 max: 1,
499 automatable: false,
500 },
501 depth: {
502 value: 0.7,
503 min: 0,
504 max: 1,
505 automatable: false,
506 },
507 rate: {
508 value: 1.5,
509 min: 0,
510 max: 8,
511 automatable: false,
512 },
513 bypass: {
514 value: true,
515 automatable: false,
516 type: BOOLEAN
517 }
518 }
519 },
520 delay: {
521 enumerable: true,
522 get: function () {
523 return this._delay;
524 },
525 set: function (value) {
526 this._delay = 0.0002 * (Math.pow(10, value) * 2);
527 this.lfoL.offset = this._delay;
528 this.lfoR.offset = this._delay;
529 this._depth = this._depth;
530 }
531 },
532 depth: {
533 enumerable: true,
534 get: function () {
535 return this._depth;
536 },
537 set: function (value) {
538 this._depth = value;
539 this.lfoL.oscillation = this._depth * this._delay;
540 this.lfoR.oscillation = this._depth * this._delay;
541 }
542 },
543 feedback: {
544 enumerable: true,
545 get: function () {
546 return this._feedback;
547 },
548 set: function (value) {
549 this._feedback = value;
550 this.feedbackGainNodeLR.gain.value = this._feedback;
551 this.feedbackGainNodeRL.gain.value = this._feedback;
552 }
553 },
554 rate: {
555 enumerable: true,
556 get: function () {
557 return this._rate;
558 },
559 set: function (value) {
560 this._rate = value;
561 this.lfoL.frequency = this._rate;
562 this.lfoR.frequency = this._rate;
563 }
564 }
565 });
566 Tuna.prototype.Compressor = function (properties) {
567 if (!properties) {
568 properties = this.getDefaults();
569 }
570 this.input = userContext.createGain();
571 this.compNode = this.activateNode = userContext.createDynamicsCompressor();
572 this.makeupNode = userContext.createGain();
573 this.output = userContext.createGain();
574
575 this.compNode.connect(this.makeupNode);
576 this.makeupNode.connect(this.output);
577
578 this.automakeup = properties.automakeup || this.defaults.automakeup.value;
579 this.makeupGain = properties.makeupGain || this.defaults.makeupGain.value;
580 this.threshold = properties.threshold || this.defaults.threshold.value;
581 this.release = properties.release || this.defaults.release.value;
582 this.attack = properties.attack || this.defaults.attack.value;
583 this.ratio = properties.ratio || this.defaults.ratio.value;
584 this.knee = properties.knee || this.defaults.knee.value;
585 this.bypass = properties.bypass || false;
586 };
587 Tuna.prototype.Compressor.prototype = Object.create(Super, {
588 name: {
589 value: "Compressor"
590 },
591 defaults: {
592 writable:true,
593 value: {
594 threshold: {
595 value: -20,
596 min: -60,
597 max: 0,
598 automatable: true
599 },
600 release: {
601 value: 250,
602 min: 10,
603 max: 2000,
604 automatable: true
605 },
606 makeupGain: {
607 value: 1,
608 min: 1,
609 max: 100,
610 automatable: true
611 },
612 attack: {
613 value: 1,
614 min: 0,
615 max: 1000,
616 automatable: true
617 },
618 ratio: {
619 value: 4,
620 min: 1,
621 max: 50,
622 automatable: true
623 },
624 knee: {
625 value: 5,
626 min: 0,
627 max: 40,
628 automatable: true
629 },
630 automakeup: {
631 value: false,
632 automatable: false,
633 type: BOOLEAN
634 },
635 bypass: {
636 value: true,
637 automatable: false,
638 type: BOOLEAN
639 }
640 }
641 },
642 computeMakeup: {
643 value: function () {
644 var magicCoefficient = 4,
645 // raise me if the output is too hot
646 c = this.compNode;
647 return -(c.threshold.value - c.threshold.value / c.ratio.value) / magicCoefficient;
648 }
649 },
650 automakeup: {
651 enumerable: true,
652 get: function () {
653 return this._automakeup;
654 },
655 set: function (value) {
656 this._automakeup = value;
657 if (this._automakeup) this.makeupGain = this.computeMakeup();
658 }
659 },
660 threshold: {
661 enumerable: true,
662 get: function () {
663 return this.compNode.threshold;
664 },
665 set: function (value) {
666 this.compNode.threshold.value = value;
667 if (this._automakeup) this.makeupGain = this.computeMakeup();
668 }
669 },
670 ratio: {
671 enumerable: true,
672 get: function () {
673 return this.compNode.ratio;
674 },
675 set: function (value) {
676 this.compNode.ratio.value = value;
677 if (this._automakeup) this.makeupGain = this.computeMakeup();
678 }
679 },
680 knee: {
681 enumerable: true,
682 get: function () {
683 return this.compNode.knee;
684 },
685 set: function (value) {
686 this.compNode.knee.value = value;
687 if (this._automakeup) this.makeupGain = this.computeMakeup();
688 }
689 },
690 attack: {
691 enumerable: true,
692 get: function () {
693 return this.compNode.attack;
694 },
695 set: function (value) {
696 this.compNode.attack.value = value / 1000;
697 }
698 },
699 release: {
700 enumerable: true,
701 get: function () {
702 return this.compNode.release;
703 },
704 set: function (value) {
705 this.compNode.release = value / 1000;
706 }
707 },
708 makeupGain: {
709 enumerable: true,
710 get: function () {
711 return this.makeupNode.gain;
712 },
713 set: function (value) {
714 this.makeupNode.gain.value = dbToWAVolume(value);
715 }
716 }
717 });
718 Tuna.prototype.Convolver = function (properties) {
719 if (!properties) {
720 properties = this.getDefaults();
721 }
722 this.input = userContext.createGain();
723 this.activateNode = userContext.createGain();
724 this.convolver = userContext.createConvolver();
725 this.dry = userContext.createGain();
726 this.filterLow = userContext.createBiquadFilter();
727 this.filterHigh = userContext.createBiquadFilter();
728 this.wet = userContext.createGain();
729 this.output = userContext.createGain();
730
731 this.activateNode.connect(this.filterLow);
732 this.activateNode.connect(this.dry);
733 this.filterLow.connect(this.filterHigh);
734 this.filterHigh.connect(this.convolver);
735 this.convolver.connect(this.wet);
736 this.wet.connect(this.output);
737 this.dry.connect(this.output);
738
739 this.dryLevel = properties.dryLevel || this.defaults.dryLevel.value;
740 this.wetLevel = properties.wetLevel || this.defaults.wetLevel.value;
741 this.highCut = properties.highCut || this.defaults.highCut.value;
742 this.buffer = properties.impulse || "../impulses/ir_rev_short.wav";
743 this.lowCut = properties.lowCut || this.defaults.lowCut.value;
744 this.level = properties.level || this.defaults.level.value;
745 this.filterHigh.type = this.filterHigh.LOWPASS;
746 this.filterLow.type = this.filterHigh.HIGHPASS;
747 this.bypass = properties.bypass || false;
748 };
749 Tuna.prototype.Convolver.prototype = Object.create(Super, {
750 name: {
751 value: "Convolver"
752 },
753 defaults: {
754 writable:true,
755 value: {
756 highCut: {
757 value: 22050,
758 min: 20,
759 max: 22050,
760 automatable: true
761 },
762 lowCut: {
763 value: 20,
764 min: 20,
765 max: 22050,
766 automatable: true
767 },
768 dryLevel: {
769 value: 1,
770 min: 0,
771 max: 1,
772 automatable: true
773 },
774 wetLevel: {
775 value: 1,
776 min: 0,
777 max: 1,
778 automatable: true
779 },
780 level: {
781 value: 1,
782 min: 0,
783 max: 1,
784 automatable: true
785 }
786 }
787 },
788 lowCut: {
789 get: function () {
790 return this.filterLow.frequency;
791 },
792 set: function (value) {
793 this.filterLow.frequency.value = value;
794 }
795 },
796 highCut: {
797 get: function () {
798 return this.filterHigh.frequency;
799 },
800 set: function (value) {
801 this.filterHigh.frequency.value = value;
802 }
803 },
804 level: {
805 get: function () {
806 return this.output.gain;
807 },
808 set: function (value) {
809 this.output.gain.value = value;
810 }
811 },
812 dryLevel: {
813 get: function () {
814 return this.dry.gain
815 },
816 set: function (value) {
817 this.dry.gain.value = value;
818 }
819 },
820 wetLevel: {
821 get: function () {
822 return this.wet.gain;
823 },
824 set: function (value) {
825 this.wet.gain.value = value;
826 }
827 },
828 buffer: {
829 enumerable: false,
830 get: function () {
831 return this.convolver.buffer;
832 },
833 set: function (impulse) {
834 var convolver = this.convolver,
835 xhr = new XMLHttpRequest();
836 if (!impulse) {
837 console.log("Tuna.Convolver.setBuffer: Missing impulse path!");
838 return;
839 }
840 xhr.open("GET", impulse, true);
841 xhr.responseType = "arraybuffer";
842 xhr.onreadystatechange = function () {
843 if (xhr.readyState === 4) {
844 if (xhr.status < 300 && xhr.status > 199 || xhr.status === 302) {
845 userContext.decodeAudioData(xhr.response, function (buffer) {
846 convolver.buffer = buffer;
847 }, function (e) {
848 if (e) console.log("Tuna.Convolver.setBuffer: Error decoding data" + e);
849 });
850 }
851 }
852 };
853 xhr.send(null);
854 }
855 }
856 });
857 Tuna.prototype.Delay = function (properties) {
858 if (!properties) {
859 properties = this.getDefaults();
860 }
861 this.input = userContext.createGain();
862 this.activateNode = userContext.createGain();
863 this.dry = userContext.createGain();
864 this.wet = userContext.createGain();
865 this.filter = userContext.createBiquadFilter();
866 this.delay = userContext.createDelayNode();
867 this.feedbackNode = userContext.createGain();
868 this.output = userContext.createGain();
869
870 this.activateNode.connect(this.delay);
871 this.activateNode.connect(this.dry);
872 this.delay.connect(this.filter);
873 this.filter.connect(this.feedbackNode);
874 this.feedbackNode.connect(this.delay);
875 this.feedbackNode.connect(this.wet);
876 this.wet.connect(this.output);
877 this.dry.connect(this.output);
878
879 this.delayTime = properties.delayTime || this.defaults.delayTime.value;
880 this.feedback = properties.feedback || this.defaults.feedback.value;
881 this.wetLevel = properties.wetLevel || this.defaults.wetLevel.value;
882 this.dryLevel = properties.dryLevel || this.defaults.dryLevel.value;
883 this.cutoff = properties.cutoff || this.defaults.cutoff.value;
884 this.filter.type = this.filter.LOWPASS;
885 this.bypass = properties.bypass || false;
886 };
887
888 Tuna.prototype.Delay.prototype = Object.create(Super, {
889 name: {
890 value: "Delay"
891 },
892 defaults: {
893 writable:true,
894 value: {
895 delayTime: {
896 value: 100,
897 min: 20,
898 max: 1000,
899 automatable: false,
900 },
901 feedback: {
902 value: 0.45,
903 min: 0,
904 max: 0.9,
905 automatable: true
906 },
907 cutoff: {
908 value: 20000,
909 min: 20,
910 max: 20000,
911 automatable: true
912 },
913 wetLevel: {
914 value: 0.5,
915 min: 0,
916 max: 1,
917 automatable: true
918 },
919 dryLevel: {
920 value: 1,
921 min: 0,
922 max: 1,
923 automatable: true
924 }
925 }
926 },
927 delayTime: {
928 enumerable: true,
929 get: function () {
930 return this.delay.delayTime;
931 },
932 set: function (value) {
933 this.delay.delayTime.value = value / 1000;
934 }
935 },
936 wetLevel: {
937 enumerable: true,
938 get: function () {
939 return this.wet.gain;
940 },
941 set: function (value) {
942 this.wet.gain.value = value;
943 }
944 },
945 dryLevel: {
946 enumerable: true,
947 get: function () {
948 return this.dry.gain;
949 },
950 set: function (value) {
951 this.dry.gain.value = value;
952 }
953 },
954 feedback: {
955 enumerable: true,
956 get: function () {
957 return this.feedbackNode.gain;
958 },
959 set: function (value) {
960 this.feedbackNode.gain.value = value;
961 }
962 },
963 cutoff: {
964 enumerable: true,
965 get: function () {
966 return this.filter.frequency;
967 },
968 set: function (value) {
969 this.filter.frequency.value = value;
970 }
971 }
972 });
973 Tuna.prototype.MoogFilter = function (properties) {
974 if (!properties) {
975 properties = this.getDefaults();
976 }
977 this.bufferSize = properties.bufferSize || this.defaults.bufferSize.value;
978
979 this.input = userContext.createGain();
980 this.activateNode = userContext.createGain();
981 this.processor = userContext.createScriptProcessor(this.bufferSize, 1, 1);
982 this.output = userContext.createGain();
983
984 this.activateNode.connect(this.processor);
985 this.processor.connect(this.output);
986
987 var in1, in2, in3, in4, out1, out2, out3, out4;
988 in1 = in2 = in3 = in4 = out1 = out2 = out3 = out4 = 0.0;
989 this.processor.onaudioprocess = function (e) {
990 var input = e.inputBuffer.getChannelData(0),
991 output = e.outputBuffer.getChannelData(0),
992 f = this.cutoff * 1.16,
993 fb = this.resonance * (1.0 - 0.15 * f * f);
994 for(var i = 0; i < input.length; i++) {
995 input[i] -= out4 * fb;
996 input[i] *= 0.35013 * (f*f)*(f*f);
997 out1 = input[i] + 0.3 * in1 + (1 - f) * out1; // Pole 1
998 in1 = input[i];
999 out2 = out1 + 0.3 * in2 + (1 - f) * out2; // Pole 2
1000 in2 = out1;
1001 out3 = out2 + 0.3 * in3 + (1 - f) * out3; // Pole 3
1002 in3 = out2;
1003 out4 = out3 + 0.3 * in4 + (1 - f) * out4; // Pole 4
1004 in4 = out3;
1005 output[i] = out4;
1006 }
1007 };
1008
1009 this.cutoff = properties.cutoff || this.defaults.cutoff.value;
1010 this.resonance = properties.resonance || this.defaults.resonance.value;
1011 this.bypass = properties.bypass || false;
1012 };
1013 Tuna.prototype.MoogFilter.prototype = Object.create(Super, {
1014 name: {
1015 value: "MoogFilter"
1016 },
1017 defaults: {
1018 writable: true,
1019 value: {
1020 bufferSize: {
1021 value: 4096,
1022 min: 256,
1023 max: 16384,
1024 automatable: false,
1025 type: INT
1026 },
1027 bypass: {
1028 value: false,
1029 automatable: false,
1030 type: BOOLEAN
1031 },
1032 cutoff: {
1033 value: 0.065,
1034 min: 0.0001,
1035 max: 1.0,
1036 automatable: false,
1037 },
1038 resonance: {
1039 value: 3.5,
1040 min: 0.0,
1041 max: 4.0,
1042 automatable: false,
1043 }
1044 }
1045 },
1046 cutoff: {
1047 enumerable: true,
1048 get: function () {
1049 return this.processor.cutoff;
1050 },
1051 set: function (value) {
1052 this.processor.cutoff = value;
1053 }
1054 },
1055 resonance: {
1056 enumerable: true,
1057 get: function () {
1058 return this.processor.resonance;
1059 },
1060 set: function (value) {
1061 this.processor.resonance = value;
1062 }
1063 }
1064 });
1065 Tuna.prototype.Overdrive = function (properties) {
1066 if (!properties) {
1067 properties = this.getDefaults();
1068 }
1069 this.input = userContext.createGain();
1070 this.activateNode = userContext.createGain();
1071 this.inputDrive = userContext.createGain();
1072 this.waveshaper = userContext.createWaveShaper();
1073 this.outputDrive = userContext.createGain();
1074 this.output = userContext.createGain();
1075
1076 this.activateNode.connect(this.inputDrive);
1077 this.inputDrive.connect(this.waveshaper);
1078 this.waveshaper.connect(this.outputDrive);
1079 this.outputDrive.connect(this.output);
1080
1081 this.ws_table = new Float32Array(this.k_nSamples);
1082 this.drive = properties.drive || this.defaults.drive.value;
1083 this.outputGain = properties.outputGain || this.defaults.outputGain.value;
1084 this.curveAmount = properties.curveAmount || this.defaults.curveAmount.value;
1085 this.algorithmIndex = properties.algorithmIndex || this.defaults.algorithmIndex.value;
1086 this.bypass = properties.bypass || false;
1087 };
1088 Tuna.prototype.Overdrive.prototype = Object.create(Super, {
1089 name: {
1090 value: "Overdrive"
1091 },
1092 defaults: {
1093 writable:true,
1094 value: {
1095 drive: {
1096 value: 1,
1097 min: 0,
1098 max: 1,
1099 automatable: true,
1100 type: FLOAT,
1101 scaled: true
1102 },
1103 outputGain: {
1104 value: 1,
1105 min: 0,
1106 max: 1,
1107 automatable: true,
1108 type: FLOAT,
1109 scaled: true
1110 },
1111 curveAmount: {
1112 value: 0.725,
1113 min: 0,
1114 max: 1,
1115 automatable: false,
1116 },
1117 algorithmIndex: {
1118 value: 0,
1119 min: 0,
1120 max: 5,
1121 automatable: false,
1122 type: INT
1123 }
1124 }
1125 },
1126 k_nSamples: {
1127 value: 8192
1128 },
1129 drive: {
1130 get: function () {
1131 return this.inputDrive.gain;
1132 },
1133 set: function (value) {
1134 this._drive = value;
1135 }
1136 },
1137 curveAmount: {
1138 get: function () {
1139 return this._curveAmount;
1140 },
1141 set: function (value) {
1142 this._curveAmount = value;
1143 if (this._algorithmIndex === undefined) {
1144 this._algorithmIndex = 0;
1145 }
1146 this.waveshaperAlgorithms[this._algorithmIndex](this._curveAmount, this.k_nSamples, this.ws_table);
1147 this.waveshaper.curve = this.ws_table;
1148 }
1149 },
1150 outputGain: {
1151 get: function () {
1152 return this.outputDrive.gain;
1153 },
1154 set: function (value) {
1155 this._outputGain = dbToWAVolume(value);
1156 }
1157 },
1158 algorithmIndex: {
1159 get: function () {
1160 return this._algorithmIndex;
1161 },
1162 set: function (value) {
1163 this._algorithmIndex = value>>0;
1164 this.curveAmount = this._curveAmount;
1165 }
1166 },
1167 waveshaperAlgorithms: {
1168 value: [
1169
1170 function (amount, n_samples, ws_table) {
1171 amount = Math.min(amount, 0.9999);
1172 var k = 2 * amount / (1 - amount),
1173 i, x;
1174 for(i = 0; i < n_samples; i++) {
1175 x = i * 2 / n_samples - 1;
1176 ws_table[i] = (1 + k) * x / (1 + k * Math.abs(x));
1177 }
1178 }, function (amount, n_samples, ws_table) {
1179 var i, x, y;
1180 for(i = 0; i < n_samples; i++) {
1181 x = i * 2 / n_samples - 1;
1182 y = ((0.5 * Math.pow((x + 1.4), 2)) - 1) * y >= 0 ? 5.8 : 1.2;
1183 ws_table[i] = tanh(y);
1184 }
1185 }, function (amount, n_samples, ws_table) {
1186 var i, x, y, a = 1 - amount;
1187 for(i = 0; i < n_samples; i++) {
1188 x = i * 2 / n_samples - 1;
1189 y = x < 0 ? -Math.pow(Math.abs(x), a + 0.04) : Math.pow(x, a);
1190 ws_table[i] = tanh(y * 2);
1191 }
1192 }, function (amount, n_samples, ws_table) {
1193 var i, x, y, abx, a = 1 - amount > 0.99 ? 0.99 : 1 - amount;
1194 for(i = 0; i < n_samples; i++) {
1195 x = i * 2 / n_samples - 1;
1196 abx = Math.abs(x);
1197 if (abx < a) y = abx;
1198 else if (abx > a) y = a + (abx - a) / (1 + Math.pow((abx - a) / (1 - a), 2));
1199 else if (abx > 1) y = abx;
1200 ws_table[i] = sign(x) * y * (1 / ((a + 1) / 2));
1201 }
1202 }, function (amount, n_samples, ws_table) { // fixed curve, amount doesn't do anything, the distortion is just from the drive
1203 var i, x;
1204 for(i = 0; i < n_samples; i++) {
1205 x = i * 2 / n_samples - 1;
1206 if (x < -0.08905) {
1207 ws_table[i] = (-3 / 4) * (1 - (Math.pow((1 - (Math.abs(x) - 0.032857)), 12)) + (1 / 3) * (Math.abs(x) - 0.032847)) + 0.01;
1208 } else if (x >= -0.08905 && x < 0.320018) {
1209 ws_table[i] = (-6.153 * (x * x)) + 3.9375 * x;
1210 } else {
1211 ws_table[i] = 0.630035;
1212 }
1213 }
1214 }, function (amount, n_samples, ws_table) {
1215 var a = 2 + Math.round(amount * 14),
1216 // we go from 2 to 16 bits, keep in mind for the UI
1217 bits = Math.round(Math.pow(2, a - 1)),
1218 // real number of quantization steps divided by 2
1219 i, x;
1220 for(i = 0; i < n_samples; i++) {
1221 x = i * 2 / n_samples - 1;
1222 ws_table[i] = Math.round(x * bits) / bits;
1223 }
1224 }]
1225 }
1226 });
1227 Tuna.prototype.Phaser = function (properties) {
1228 if (!properties) {
1229 properties = this.getDefaults();
1230 }
1231 this.input = userContext.createGain();
1232 this.splitter = this.activateNode = userContext.createChannelSplitter(2);
1233 this.filtersL = [];
1234 this.filtersR = [];
1235 this.feedbackGainNodeL = userContext.createGain();
1236 this.feedbackGainNodeR = userContext.createGain();
1237 this.merger = userContext.createChannelMerger(2);
1238 this.filteredSignal = userContext.createGain();
1239 this.output = userContext.createGain();
1240 this.lfoL = new userInstance.LFO({
1241 target: this.filtersL,
1242 callback: this.callback
1243 });
1244 this.lfoR = new userInstance.LFO({
1245 target: this.filtersR,
1246 callback: this.callback
1247 });
1248
1249 var i = this.stage;
1250 while(i--) {
1251 this.filtersL[i] = userContext.createBiquadFilter();
1252 this.filtersR[i] = userContext.createBiquadFilter();
1253 this.filtersL[i].type = this.filtersL[i].ALLPASS;
1254 this.filtersR[i].type = this.filtersR[i].ALLPASS;
1255 }
1256 this.input.connect(this.splitter);
1257 this.input.connect(this.output);
1258 this.splitter.connect(this.filtersL[0], 0, 0);
1259 this.splitter.connect(this.filtersR[0], 1, 0);
1260 this.connectInOrder(this.filtersL);
1261 this.connectInOrder(this.filtersR);
1262 this.filtersL[this.stage - 1].connect(this.feedbackGainNodeL);
1263 this.filtersL[this.stage - 1].connect(this.merger, 0, 0);
1264 this.filtersR[this.stage - 1].connect(this.feedbackGainNodeR);
1265 this.filtersR[this.stage - 1].connect(this.merger, 0, 1);
1266 this.feedbackGainNodeL.connect(this.filtersL[0]);
1267 this.feedbackGainNodeR.connect(this.filtersR[0]);
1268 this.merger.connect(this.output);
1269
1270 this.rate = properties.rate || this.defaults.rate.value;
1271 this.baseModulationFrequency = properties.baseModulationFrequency || this.defaults.baseModulationFrequency.value;
1272 this.depth = properties.depth || this.defaults.depth.value;
1273 this.feedback = properties.feedback || this.defaults.feedback.value;
1274 this.stereoPhase = properties.stereoPhase || this.defaults.stereoPhase.value;
1275
1276 this.lfoL.activate(true);
1277 this.lfoR.activate(true);
1278 this.bypass = properties.bypass || false;
1279 };
1280 Tuna.prototype.Phaser.prototype = Object.create(Super, {
1281 name: {
1282 value: "Phaser"
1283 },
1284 stage: {
1285 value: 4
1286 },
1287 defaults: {
1288 writable:true,
1289 value: {
1290 rate: {
1291 value: 0.1,
1292 min: 0,
1293 max: 8,
1294 automatable: false,
1295 },
1296 depth: {
1297 value: 0.6,
1298 min: 0,
1299 max: 1,
1300 automatable: false,
1301 },
1302 feedback: {
1303 value: 0.7,
1304 min: 0,
1305 max: 1,
1306 automatable: false,
1307 },
1308 stereoPhase: {
1309 value: 40,
1310 min: 0,
1311 max: 180,
1312 automatable: false,
1313 },
1314 baseModulationFrequency: {
1315 value: 700,
1316 min: 500,
1317 max: 1500,
1318 automatable: false,
1319 }
1320 }
1321 },
1322 callback: {
1323 value: function (filters, value) {
1324 for(var stage = 0; stage < 4; stage++) {
1325 filters[stage].frequency.value = value;
1326 }
1327 }
1328 },
1329 depth: {
1330 get: function () {
1331 return this._depth;
1332 },
1333 set: function (value) {
1334 this._depth = value;
1335 this.lfoL.oscillation = this._baseModulationFrequency * this._depth;
1336 this.lfoR.oscillation = this._baseModulationFrequency * this._depth;
1337 }
1338 },
1339 rate: {
1340 get: function () {
1341 return this._rate;
1342 },
1343 set: function (value) {
1344 this._rate = value;
1345 this.lfoL.frequency = this._rate;
1346 this.lfoR.frequency = this._rate;
1347 }
1348 },
1349 baseModulationFrequency: {
1350 enumerable: true,
1351 get: function () {
1352 return this._baseModulationFrequency;
1353 },
1354 set: function (value) {
1355 this._baseModulationFrequency = value;
1356 this.lfoL.offset = this._baseModulationFrequency;
1357 this.lfoR.offset = this._baseModulationFrequency;
1358 this._depth = this._depth;
1359 }
1360 },
1361 feedback: {
1362 get: function () {
1363 return this._feedback;
1364 },
1365 set: function (value) {
1366 this._feedback = value;
1367 this.feedbackGainNodeL.gain.value = this._feedback;
1368 this.feedbackGainNodeR.gain.value = this._feedback;
1369 }
1370 },
1371 stereoPhase: {
1372 get: function () {
1373 return this._stereoPhase;
1374 },
1375 set: function (value) {
1376 this._stereoPhase = value;
1377 var newPhase = this.lfoL._phase + this._stereoPhase * Math.PI / 180;
1378 newPhase = fmod(newPhase, 2 * Math.PI);
1379 this.lfoR._phase = newPhase;
1380 }
1381 }
1382 });
1383 Tuna.prototype.Tremolo = function (properties) {
1384 if (!properties) {
1385 properties = this.getDefaults();
1386 }
1387 this.input = userContext.createGain();
1388 this.splitter = this.activateNode = userContext.createChannelSplitter(2), this.amplitudeL = userContext.createGain(), this.amplitudeR = userContext.createGain(), this.merger = userContext.createChannelMerger(2), this.output = userContext.createGain();
1389 this.lfoL = new userInstance.LFO({
1390 target: this.amplitudeL.gain,
1391 callback: pipe
1392 });
1393 this.lfoR = new userInstance.LFO({
1394 target: this.amplitudeR.gain,
1395 callback: pipe
1396 });
1397
1398 this.input.connect(this.splitter);
1399 this.splitter.connect(this.amplitudeL, 0);
1400 this.splitter.connect(this.amplitudeR, 1);
1401 this.amplitudeL.connect(this.merger, 0, 0);
1402 this.amplitudeR.connect(this.merger, 0, 1);
1403 this.merger.connect(this.output);
1404
1405 this.rate = properties.rate || this.defaults.rate.value;
1406 this.intensity = properties.intensity || this.defaults.intensity.value;
1407 this.stereoPhase = properties.stereoPhase || this.defaults.stereoPhase.value;
1408
1409 this.lfoL.offset = 1 - (this.intensity / 2);
1410 this.lfoR.offset = 1 - (this.intensity / 2);
1411 this.lfoL.phase = this.stereoPhase * Math.PI / 180;
1412
1413 this.lfoL.activate(true);
1414 this.lfoR.activate(true);
1415 this.bypass = properties.bypass || false;
1416 };
1417 Tuna.prototype.Tremolo.prototype = Object.create(Super, {
1418 name: {
1419 value: "Tremolo"
1420 },
1421 defaults: {
1422 writable:true,
1423 value: {
1424 intensity: {
1425 value: 0.3,
1426 min: 0,
1427 max: 1,
1428 automatable: false,
1429 },
1430 stereoPhase: {
1431 value: 0,
1432 min: 0,
1433 max: 180,
1434 automatable: false,
1435 },
1436 rate: {
1437 value: 5,
1438 min: 0.1,
1439 max: 11,
1440 automatable: false,
1441 }
1442 }
1443 },
1444 intensity: {
1445 enumerable: true,
1446 get: function () {
1447 return this._intensity;
1448 },
1449 set: function (value) {
1450 this._intensity = value;
1451 this.lfoL.offset = 1 - this._intensity / 2;
1452 this.lfoR.offset = 1 - this._intensity / 2;
1453 this.lfoL.oscillation = this._intensity;
1454 this.lfoR.oscillation = this._intensity;
1455 }
1456 },
1457 rate: {
1458 enumerable: true,
1459 get: function () {
1460 return this._rate;
1461 },
1462 set: function (value) {
1463 this._rate = value;
1464 this.lfoL.frequency = this._rate;
1465 this.lfoR.frequency = this._rate;
1466 }
1467 },
1468 stereoPhase: {
1469 enumerable: true,
1470 get: function () {
1471 return this._rate;
1472 },
1473 set: function (value) {
1474 this._stereoPhase = value;
1475 var newPhase = this.lfoL._phase + this._stereoPhase * Math.PI / 180;
1476 newPhase = fmod(newPhase, 2 * Math.PI);
1477 this.lfoR.phase = newPhase;
1478 }
1479 }
1480 });
1481 Tuna.prototype.WahWah = function (properties) {
1482 if (!properties) {
1483 properties = this.getDefaults();
1484 }
1485 this.input = userContext.createGain();
1486 this.activateNode = userContext.createGain();
1487 this.envelopeFollower = new userInstance.EnvelopeFollower({
1488 target: this,
1489 callback: function (context, value) {
1490 context.sweep = value;
1491 }
1492 });
1493 this.filterBp = userContext.createBiquadFilter();
1494 this.filterPeaking = userContext.createBiquadFilter();
1495 this.output = userContext.createGain();
1496
1497 //Connect AudioNodes
1498 this.activateNode.connect(this.filterBp);
1499 this.filterBp.connect(this.filterPeaking);
1500 this.filterPeaking.connect(this.output);
1501
1502 //Set Properties
1503 this.init();
1504 this.automode = properties.enableAutoMode || this.defaults.automode.value;
1505 this.resonance = properties.resonance || this.defaults.resonance.value;
1506 this.sensitivity = properties.sensitivity || this.defaults.sensitivity.value;
1507 this.baseFrequency = properties.baseFrequency || this.defaults.baseFrequency.value;
1508 this.excursionOctaves = properties.excursionOctaves || this.defaults.excursionOctaves.value;
1509 this.sweep = properties.sweep || this.defaults.sweep.value;
1510
1511 this.activateNode.gain.value = 2;
1512 this.envelopeFollower.activate(true);
1513 this.bypass = properties.bypass || false;
1514 };
1515 Tuna.prototype.WahWah.prototype = Object.create(Super, {
1516 name: {
1517 value: "WahWah"
1518 },
1519 defaults: {
1520 writable:true,
1521 value: {
1522 automode: {
1523 value: true,
1524 automatable: false,
1525 type: BOOLEAN
1526 },
1527 baseFrequency: {
1528 value: 0.5,
1529 min: 0,
1530 max: 1,
1531 automatable: false,
1532 },
1533 excursionOctaves: {
1534 value: 2,
1535 min: 1,
1536 max: 6,
1537 automatable: false,
1538 },
1539 sweep: {
1540 value: 0.2,
1541 min: 0,
1542 max: 1,
1543 automatable: false,
1544 },
1545 resonance: {
1546 value: 10,
1547 min: 1,
1548 max: 100,
1549 automatable: false,
1550 },
1551 sensitivity: {
1552 value: 0.5,
1553 min: -1,
1554 max: 1,
1555 automatable: false,
1556 }
1557 }
1558 },
1559 activateCallback: {
1560 value: function (value) {
1561 this.automode = value;
1562 }
1563 },
1564 automode: {
1565 get: function () {
1566 return this._automode;
1567 },
1568 set: function (value) {
1569 this._automode = value;
1570 if (value) {
1571 this.activateNode.connect(this.envelopeFollower.input);
1572 this.envelopeFollower.activate(true);
1573 } else {
1574 this.envelopeFollower.activate(false);
1575 this.activateNode.disconnect();
1576 this.activateNode.connect(this.filterBp);
1577 }
1578 }
1579 },
1580 sweep: {
1581 enumerable: true,
1582 get: function () {
1583 return this._sweep.value;
1584 },
1585 set: function (value) {
1586 this._sweep = Math.pow(value > 1 ? 1 : value < 0 ? 0 : value, this._sensitivity);
1587 this.filterBp.frequency.value = this._baseFrequency + this._excursionFrequency * this._sweep;
1588 this.filterPeaking.frequency.value = this._baseFrequency + this._excursionFrequency * this._sweep;
1589 }
1590 },
1591 baseFrequency: {
1592 enumerable: true,
1593 get: function () {
1594 return this._baseFrequency;
1595 },
1596 set: function (value) {
1597 this._baseFrequency = 50 * Math.pow(10, value * 2);
1598 this._excursionFrequency = Math.min(this.sampleRate / 2, this.baseFrequency * Math.pow(2, this._excursionOctaves));
1599 this.filterBp.frequency.value = this._baseFrequency + this._excursionFrequency * this._sweep;
1600 this.filterPeaking.frequency.value = this._baseFrequency + this._excursionFrequency * this._sweep;
1601 }
1602 },
1603 excursionOctaves: {
1604 enumerable: true,
1605 get: function () {
1606 return this._excursionOctaves;
1607 },
1608 set: function (value) {
1609 this._excursionOctaves = value;
1610 this._excursionFrequency = Math.min(this.sampleRate / 2, this.baseFrequency * Math.pow(2, this._excursionOctaves));
1611 this.filterBp.frequency.value = this._baseFrequency + this._excursionFrequency * this._sweep;
1612 this.filterPeaking.frequency.value = this._baseFrequency + this._excursionFrequency * this._sweep;
1613 }
1614 },
1615 sensitivity: {
1616 enumerable: true,
1617 get: function () {
1618 return this._sensitivity;
1619 },
1620 set: function (value) {
1621 this._sensitivity = Math.pow(10, value);
1622 }
1623 },
1624 resonance: {
1625 enumerable: true,
1626 get: function () {
1627 return this._resonance;
1628 },
1629 set: function (value) {
1630 this._resonance = value;
1631 this.filterPeaking.Q = this._resonance;
1632 }
1633 },
1634 init: {
1635 value: function () {
1636 this.output.gain.value = 1;
1637 this.filterPeaking.type = 5;
1638 this.filterBp.type = 2;
1639 this.filterPeaking.frequency.value = 100;
1640 this.filterPeaking.gain.value = 20;
1641 this.filterPeaking.Q.value = 5;
1642 this.filterBp.frequency.value = 100;
1643 this.filterBp.Q.value = 1;
1644 this.sampleRate = userContext.sampleRate;
1645 }
1646 }
1647 });
1648 Tuna.prototype.EnvelopeFollower = function (properties) {
1649 if (!properties) {
1650 properties = this.getDefaults();
1651 }
1652 this.input = userContext.createGain();
1653 this.jsNode = this.output = userContext.createScriptProcessor(this.buffersize, 1, 1);
1654
1655 this.input.connect(this.output);
1656
1657 this.attackTime = properties.attackTime || this.defaults.attackTime.value;
1658 this.releaseTime = properties.releaseTime || this.defaults.releaseTime.value;
1659 this._envelope = 0;
1660 this.target = properties.target || {};
1661 this.callback = properties.callback || function () {};
1662 };
1663 Tuna.prototype.EnvelopeFollower.prototype = Object.create(Super, {
1664 name: {
1665 value: "EnvelopeFollower"
1666 },
1667 defaults: {
1668 value: {
1669 attackTime: {
1670 value: 0.003,
1671 min: 0,
1672 max: 0.5,
1673 automatable: false,
1674 },
1675 releaseTime: {
1676 value: 0.5,
1677 min: 0,
1678 max: 0.5,
1679 automatable: false,
1680 }
1681 }
1682 },
1683 buffersize: {
1684 value: 256
1685 },
1686 envelope: {
1687 value: 0
1688 },
1689 sampleRate: {
1690 value: 44100
1691 },
1692 attackTime: {
1693 enumerable: true,
1694 get: function () {
1695 return this._attackTime;
1696 },
1697 set: function (value) {
1698 this._attackTime = value;
1699 this._attackC = Math.exp(-1 / this._attackTime * this.sampleRate / this.buffersize);
1700 }
1701 },
1702 releaseTime: {
1703 enumerable: true,
1704 get: function () {
1705 return this._releaseTime;
1706 },
1707 set: function (value) {
1708 this._releaseTime = value;
1709 this._releaseC = Math.exp(-1 / this._releaseTime * this.sampleRate / this.buffersize);
1710 }
1711 },
1712 callback: {
1713 get: function () {
1714 return this._callback;
1715 },
1716 set: function (value) {
1717 if (typeof value === "function") {
1718 this._callback = value;
1719 } else {
1720 console.error("tuna.js: " + this.name + ": Callback must be a function!");
1721 }
1722 }
1723 },
1724 target: {
1725 get: function () {
1726 return this._target;
1727 },
1728 set: function (value) {
1729 this._target = value;
1730 }
1731 },
1732 activate: {
1733 value: function (doActivate) {
1734 this.activated = doActivate;
1735 if (doActivate) {
1736 this.jsNode.connect(userContext.destination);
1737 this.jsNode.onaudioprocess = this.returnCompute(this);
1738 } else {
1739 this.jsNode.disconnect();
1740 this.jsNode.onaudioprocess = null;
1741 }
1742 }
1743 },
1744 returnCompute: {
1745 value: function (instance) {
1746 return function (event) {
1747 instance.compute(event);
1748 };
1749 }
1750 },
1751 compute: {
1752 value: function (event) {
1753 var count = event.inputBuffer.getChannelData(0).length,
1754 channels = event.inputBuffer.numberOfChannels,
1755 current, chan, rms, i;
1756 chan = rms = i = 0;
1757 if (channels > 1) { //need to mixdown
1758 for(i = 0; i < count; ++i) {
1759 for(; chan < channels; ++chan) {
1760 current = event.inputBuffer.getChannelData(chan)[i];
1761 rms += (current * current) / channels;
1762 }
1763 }
1764 } else {
1765 for(i = 0; i < count; ++i) {
1766 current = event.inputBuffer.getChannelData(0)[i];
1767 rms += (current * current);
1768 }
1769 }
1770 rms = Math.sqrt(rms);
1771
1772 if (this._envelope < rms) {
1773 this._envelope *= this._attackC;
1774 this._envelope += (1 - this._attackC) * rms;
1775 } else {
1776 this._envelope *= this._releaseC;
1777 this._envelope += (1 - this._releaseC) * rms;
1778 }
1779 this._callback(this._target, this._envelope);
1780 }
1781 }
1782 });
1783
1784 // Low-frequency oscillation
1785 Tuna.prototype.LFO = function (properties) {
1786 //Instantiate AudioNode
1787 this.output = userContext.createScriptProcessor(256, 1, 1);
1788 this.activateNode = userContext.destination;
1789
1790 //Set Properties
1791 this.frequency = properties.frequency || this.defaults.frequency.value;
1792 this.offset = properties.offset || this.defaults.offset.value;
1793 this.oscillation = properties.oscillation || this.defaults.oscillation.value;
1794 this.phase = properties.phase || this.defaults.phase.value;
1795 this.target = properties.target || {};
1796 this.output.onaudioprocess = this.callback(properties.callback || function () {});
1797 this.bypass = properties.bypass || false;
1798 };
1799 Tuna.prototype.LFO.prototype = Object.create(Super, {
1800 name: {
1801 value: "LFO"
1802 },
1803 bufferSize: {
1804 value: 256
1805 },
1806 sampleRate: {
1807 value: 44100
1808 },
1809 defaults: {
1810 value: {
1811 frequency: {
1812 value: 1,
1813 min: 0,
1814 max: 20,
1815 automatable: false,
1816 },
1817 offset: {
1818 value: 0.85,
1819 min: 0,
1820 max: 22049,
1821 automatable: false,
1822 },
1823 oscillation: {
1824 value: 0.3,
1825 min: -22050,
1826 max: 22050,
1827 automatable: false,
1828 },
1829 phase: {
1830 value: 0,
1831 min: 0,
1832 max: 2 * Math.PI,
1833 automatable: false,
1834 }
1835 }
1836 },
1837 frequency: {
1838 get: function () {
1839 return this._frequency;
1840 },
1841 set: function (value) {
1842 this._frequency = value;
1843 this._phaseInc = 2 * Math.PI * this._frequency * this.bufferSize / this.sampleRate;
1844 }
1845 },
1846 offset: {
1847 get: function () {
1848 return this._offset;
1849 },
1850 set: function (value) {
1851 this._offset = value;
1852 }
1853 },
1854 oscillation: {
1855 get: function () {
1856 return this._oscillation;
1857 },
1858 set: function (value) {
1859 this._oscillation = value;
1860 }
1861 },
1862 phase: {
1863 get: function () {
1864 return this._phase;
1865 },
1866 set: function (value) {
1867 this._phase = value;
1868 }
1869 },
1870 target: {
1871 get: function () {
1872 return this._target;
1873 },
1874 set: function (value) {
1875 this._target = value;
1876 }
1877 },
1878 activate: {
1879 value: function (doActivate) {
1880 if (!doActivate) {
1881 this.output.disconnect(userContext.destination);
1882 } else {
1883 this.output.connect(userContext.destination);
1884 }
1885 }
1886 },
1887 callback: {
1888 value: function (callback) {
1889 var that = this;
1890 return function () {
1891 that._phase += that._phaseInc;
1892 if (that._phase > 2 * Math.PI) {
1893 that._phase = 0;
1894 }
1895 callback(that._target, that._offset + that._oscillation * Math.sin(that._phase));
1896 };
1897 }
1898 }
1899 });
1900
1901 /* Panner
1902 ------------------------------------------------*/
1903 Tuna.prototype.Panner = function (properties) {
1904 if (!properties) {
1905 properties = this.getDefaults();
1906 }
1907 this.input = userContext.createGain();
1908 this.activateNode = userContext.createGain();
1909 this.output = userContext.createGain();
1910 this.panner = userContext.createPanner();
1911
1912 this.activateNode.connect(this.panner);
1913 this.panner.connect(this.output);
1914
1915 this.bypass = properties.bypass || false;
1916 this.x = properties.x || 0;
1917 this.y = properties.y || 1;
1918 this.z = properties.z || 1;
1919 this.panningModel = properties.panningModel || 0;
1920 this.distanceModel = properties.distanceModel || 0;
1921 };
1922
1923 var PannerPosition = function(type) {
1924 return {
1925 enumerable: true,
1926 get: function () {
1927 return this["_" + type];
1928 },
1929 set: function (value) {
1930 this["_" + type] = value;
1931 this.panner.setPosition(this._x || 0, this._y || 0, this._z || 0);
1932 }
1933 }
1934 };
1935
1936 var PannerModel = function(type) {
1937 return {
1938 enumerable: true,
1939 get: function () {
1940 return this["_" + type];
1941 },
1942 set: function (value) {
1943 this["_" + type] = value;
1944 this.panner[type] = value;
1945 }
1946 }
1947 };
1948
1949 var Clamp = function(value, min, max, automatable) {
1950 return {
1951 value: value,
1952 min: min,
1953 max: max,
1954 automatable: automatable
1955 };
1956 };
1957
1958 Tuna.prototype.Panner.prototype = Object.create(Super, {
1959 name: {
1960 value: "Panner"
1961 },
1962 defaults: {
1963 writable:true,
1964 value: {
1965 x: Clamp(0, -20, 20, false),
1966 y: Clamp(0, -20, 20, false),
1967 z: Clamp(0, -20, 20, false),
1968 distanceModel: Clamp(0, 0, 2, false),
1969 panningModel: Clamp(0, 0, 2, false)
1970 }
1971 },
1972 x: PannerPosition("x"),
1973 y: PannerPosition("y"),
1974 z: PannerPosition("z"),
1975 panningModel: PannerModel("panningModel"),
1976 distanceModel: PannerModel("distanceModel")
1977 });
1978
1979 /* Volume
1980 ------------------------------------------------*/
1981 Tuna.prototype.Volume = function (properties) {
1982 if (!properties) {
1983 properties = this.getDefaults();
1984 }
1985
1986 this.input = userContext.createGain();
1987 this.activateNode = userContext.createGain();
1988 this.output = userContext.createGain();
1989
1990 this.activateNode.connect(this.output);
1991
1992 this.bypass = properties.bypass || false;
1993 this.amount = properties.amount || this.defaults.amount.value;
1994 };
1995
1996 Tuna.prototype.Volume.prototype = Object.create(Super, {
1997 name: {
1998 value: "Volume"
1999 },
2000 defaults: {
2001 writable:true,
2002 value: {
2003 amount: Clamp(0, 0, 2, false)
2004 }
2005 },
2006 amount: {
2007 enumerable: true,
2008 get: function () {
2009 return this._volume;
2010 },
2011 set: function (value) {
2012 this._volume = value;
2013 this.activateNode.gain.value = value;
2014 }
2015 }
2016 });
2017
2018 /* Frequency
2019 ------------------------------------------------*/
2020 Tuna.prototype.Frequency = function (properties) {
2021 if (!properties) {
2022 properties = this.getDefaults();
2023 }
2024
2025 this.trebleFilter = userContext.createBiquadFilter();
2026 this.trebleFilter.type = this.trebleFilter.HIGHSHELF;
2027 this.trebleFilter.frequency.value = 8000; // 1k+
2028 this.trebleFilter.Q.value = 0;
2029 this.midtoneFilter = userContext.createBiquadFilter();
2030 this.midtoneFilter.type = this.midtoneFilter.PEAKING;
2031 this.midtoneFilter.frequency.value = 1000; // 200-1k
2032 this.midtoneFilter.Q.value = 0;
2033 this.bassFilter = userContext.createBiquadFilter();
2034 this.bassFilter.type = this.bassFilter.LOWSHELF;
2035 this.bassFilter.frequency.value = 200; // 60-200k
2036 this.bassFilter.Q.value = 0;
2037
2038 this.input = userContext.createGain();
2039 this.activateNode = userContext.createGain();
2040 this.output = userContext.createGain();
2041
2042 this.activateNode.connect(this.bassFilter);
2043 this.bassFilter.connect(this.midtoneFilter);
2044 this.midtoneFilter.connect(this.trebleFilter);
2045 this.trebleFilter.connect(this.output);
2046
2047 this.bypass = properties.bypass || false;
2048 this.volume = properties.volume || false;
2049 this.treble = properties.treble || false;
2050 this.midtone = properties.midtone || false;
2051 this.bass = properties.bass || false;
2052 };
2053
2054 var GainValue = function(type, nodeId) {
2055 return {
2056 enumerable: true,
2057 get: function () {
2058 return this["_" + type];
2059 },
2060 set: function (value) {
2061 this["_" + type] = value;
2062 this[nodeId || type + "Filter"].gain.value = value;
2063 }
2064 };
2065 };
2066
2067 Tuna.prototype.Frequency.prototype = Object.create(Super, {
2068 name: {
2069 value: "Frequency"
2070 },
2071 defaults: {
2072 writable:true,
2073 value: {
2074 volume: Clamp(1, 0, 2, false),
2075 treble: Clamp(0, -20, 20, false),
2076 midtone: Clamp(0, -20, 20, false),
2077 bass: Clamp(0, -20, 20, false)
2078 }
2079 },
2080 volume: GainValue("volume", "activateNode"),
2081 treble: GainValue("treble"),
2082 midtone: GainValue("midtone"),
2083 bass: GainValue("bass")
2084 });
2085
2086 if (typeof define === "function") {
2087 define("Tuna", [], function () {
2088 return Tuna;
2089 });
2090 } else {
2091 window.Tuna = Tuna;
2092 }
2093})(this);
Note: See TracBrowser for help on using the repository browser.