tuna.js 70 KB

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