├── .eslintrc.json
├── .gitignore
├── CONTRIBUTE.md
├── README.md
├── impulses
├── Sweetspot1M.wav
├── impulse_guitar.wav
├── impulse_rev.wav
└── ir_rev_short.wav
├── package-lock.json
├── package.json
├── tests
├── README.md
├── server.js
└── tests.js
├── tuna-min.js
└── tuna.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "indent": [2, 4],
4 | "quotes": [2, "double"],
5 | "semi": [2, "always"],
6 | "no-console": [0]
7 | },
8 | "env": {
9 | "browser": true
10 | },
11 | "extends": "eslint:recommended"
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | tests/bower_components
2 | .DS_Store
3 | node_modules/
4 | index.html
5 | song.mp3
6 | drums.wav
7 | .idea/
--------------------------------------------------------------------------------
/CONTRIBUTE.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ====
3 | Tuna.js is supposed to be an open source effort, so all pull requests are welcome. We're always looking for more effects, but there's a need for tests as well. Any other creative ideas are also welcome, of course.
4 |
5 | The intent of the project is to create an extensive library of audio effects that can be used in any Web Audio application. There's no intent to do more than that.
6 |
7 | If you've created an application that uses Tuna - feel free to add a pull request to add a link and short description of you application in the main README.md file!
8 |
9 | Who's pressing the red button?
10 | ====
11 | Tuna.js began as a hobby project of mine, @Theodeus, and were eventually brought in under the @Dinahmoe umbrella. The framework was further developed at @Dinahmoe, with the help of Alessandro Saccoia, while I was working there and finally released as open source as Jam With Chrome launched. The project then stagnated for a while until I left @Dinahmoe and was graciously allowed to keep maintaining the project.
12 |
13 | Along with me is @tencircles who's the current Technical Director at @Dinahmoe, and @alesaccoia, freelancing DSP guru.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | tuna
2 | ====
3 |
4 | An audio effects library for the Web Audio API.
5 |
6 |
7 |
8 | Effect list:
9 | ====
10 |
11 | - Overdrive (6 different algorithms)
12 | - Filter
13 | - Cabinet
14 | - Delay
15 | - Convolver (Reverb)
16 | - Compressor
17 | - WahWah
18 | - Tremolo
19 | - Phaser
20 | - Chorus
21 | - Bitcrusher
22 | - Moog Filter
23 | - Ping Pong Delay
24 | - Panner (needs polyfilling, see Panner section [in the wiki] (https://github.com/Theodeus/tuna/wiki)
25 | - Gain
26 |
27 |
28 | Usage
29 | ====
30 |
31 | ```
32 | npm install tunajs
33 | ```
34 |
35 | Check the wiki: https://github.com/Theodeus/tuna/wiki/Getting-started
36 |
37 | Or a live example: http://codepen.io/Theodeus/pen/oxKjmy?editors=0010
38 |
39 | In the wild
40 | ===
41 | This is a very incomplete list of places where Tuna.js is used.
42 |
43 | http://www.jamwithchrome.com/ - Jam With Chrome allows you to jam online with your mates across the globe using an assortment of instruments and effects. There's even a mode for dummies!
44 |
45 | https://github.com/selfrefactor/tuna-player - A React.js/Electron wrapper around Tuna.js!
46 |
47 | https://slasher.chillertv.com/ - interactive experience for the TV show Slasher
48 |
49 | http://looplabs.com/beta - Looplabs is a collaborative cloud based music studio that lets anyone, regardless of technical skills or ability, quickly and easily make professional quality music anywhere, anytime and with anyone.
50 |
51 | http://www.websynths.com/ - Browser-based microtonal midi instrument
52 |
53 | https://www.npmjs.com/package/react-music - Make music with React!
54 |
55 | http://bapjs.org/ - Beat making toolkit
56 |
57 | https://github.com/jamespfarrell/json-to-web-audio - Make music from Json with json-to-web-audio
58 |
59 | http://errozero.co.uk/acid-machine/ - Acid Machine 2, which is exactly what it sounds like! Add up to two Tuna effects per instrument.
60 |
--------------------------------------------------------------------------------
/impulses/Sweetspot1M.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Theodeus/tuna/7d55eb95515f1aba6f51b2efe26efbd20bd2163a/impulses/Sweetspot1M.wav
--------------------------------------------------------------------------------
/impulses/impulse_guitar.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Theodeus/tuna/7d55eb95515f1aba6f51b2efe26efbd20bd2163a/impulses/impulse_guitar.wav
--------------------------------------------------------------------------------
/impulses/impulse_rev.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Theodeus/tuna/7d55eb95515f1aba6f51b2efe26efbd20bd2163a/impulses/impulse_rev.wav
--------------------------------------------------------------------------------
/impulses/ir_rev_short.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Theodeus/tuna/7d55eb95515f1aba6f51b2efe26efbd20bd2163a/impulses/ir_rev_short.wav
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tunajs",
3 | "version": "1.0.15",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.0.0",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
10 | "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
11 | "dev": true,
12 | "requires": {
13 | "@babel/highlight": "^7.0.0"
14 | }
15 | },
16 | "@babel/highlight": {
17 | "version": "7.0.0",
18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
19 | "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
20 | "dev": true,
21 | "requires": {
22 | "chalk": "^2.0.0",
23 | "esutils": "^2.0.2",
24 | "js-tokens": "^4.0.0"
25 | }
26 | },
27 | "accepts": {
28 | "version": "1.3.7",
29 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
30 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
31 | "dev": true,
32 | "requires": {
33 | "mime-types": "~2.1.24",
34 | "negotiator": "0.6.2"
35 | }
36 | },
37 | "acorn": {
38 | "version": "5.7.4",
39 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
40 | "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==",
41 | "dev": true
42 | },
43 | "acorn-jsx": {
44 | "version": "4.1.1",
45 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz",
46 | "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==",
47 | "dev": true,
48 | "requires": {
49 | "acorn": "^5.0.3"
50 | }
51 | },
52 | "ajv": {
53 | "version": "6.5.4",
54 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz",
55 | "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==",
56 | "dev": true,
57 | "requires": {
58 | "fast-deep-equal": "^2.0.1",
59 | "fast-json-stable-stringify": "^2.0.0",
60 | "json-schema-traverse": "^0.4.1",
61 | "uri-js": "^4.2.2"
62 | }
63 | },
64 | "ajv-keywords": {
65 | "version": "3.2.0",
66 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
67 | "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
68 | "dev": true
69 | },
70 | "ansi-escapes": {
71 | "version": "3.1.0",
72 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
73 | "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
74 | "dev": true
75 | },
76 | "ansi-regex": {
77 | "version": "3.0.0",
78 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
79 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
80 | "dev": true
81 | },
82 | "ansi-styles": {
83 | "version": "3.2.1",
84 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
85 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
86 | "dev": true,
87 | "requires": {
88 | "color-convert": "^1.9.0"
89 | }
90 | },
91 | "argparse": {
92 | "version": "1.0.10",
93 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
94 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
95 | "dev": true,
96 | "requires": {
97 | "sprintf-js": "~1.0.2"
98 | }
99 | },
100 | "array-flatten": {
101 | "version": "1.1.1",
102 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
103 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
104 | "dev": true
105 | },
106 | "array-union": {
107 | "version": "1.0.2",
108 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
109 | "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
110 | "dev": true,
111 | "requires": {
112 | "array-uniq": "^1.0.1"
113 | }
114 | },
115 | "array-uniq": {
116 | "version": "1.0.3",
117 | "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
118 | "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
119 | "dev": true
120 | },
121 | "arrify": {
122 | "version": "1.0.1",
123 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
124 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
125 | "dev": true
126 | },
127 | "balanced-match": {
128 | "version": "1.0.0",
129 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
130 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
131 | "dev": true
132 | },
133 | "body-parser": {
134 | "version": "1.19.0",
135 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
136 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
137 | "dev": true,
138 | "requires": {
139 | "bytes": "3.1.0",
140 | "content-type": "~1.0.4",
141 | "debug": "2.6.9",
142 | "depd": "~1.1.2",
143 | "http-errors": "1.7.2",
144 | "iconv-lite": "0.4.24",
145 | "on-finished": "~2.3.0",
146 | "qs": "6.7.0",
147 | "raw-body": "2.4.0",
148 | "type-is": "~1.6.17"
149 | },
150 | "dependencies": {
151 | "debug": {
152 | "version": "2.6.9",
153 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
154 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
155 | "dev": true,
156 | "requires": {
157 | "ms": "2.0.0"
158 | }
159 | },
160 | "ms": {
161 | "version": "2.0.0",
162 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
163 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
164 | "dev": true
165 | }
166 | }
167 | },
168 | "brace-expansion": {
169 | "version": "1.1.11",
170 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
171 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
172 | "dev": true,
173 | "requires": {
174 | "balanced-match": "^1.0.0",
175 | "concat-map": "0.0.1"
176 | }
177 | },
178 | "bytes": {
179 | "version": "3.1.0",
180 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
181 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
182 | "dev": true
183 | },
184 | "caller-path": {
185 | "version": "0.1.0",
186 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
187 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
188 | "dev": true,
189 | "requires": {
190 | "callsites": "^0.2.0"
191 | }
192 | },
193 | "callsites": {
194 | "version": "0.2.0",
195 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
196 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
197 | "dev": true
198 | },
199 | "chalk": {
200 | "version": "2.4.1",
201 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
202 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
203 | "dev": true,
204 | "requires": {
205 | "ansi-styles": "^3.2.1",
206 | "escape-string-regexp": "^1.0.5",
207 | "supports-color": "^5.3.0"
208 | }
209 | },
210 | "chardet": {
211 | "version": "0.7.0",
212 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
213 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
214 | "dev": true
215 | },
216 | "circular-json": {
217 | "version": "0.3.3",
218 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
219 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
220 | "dev": true
221 | },
222 | "cli-cursor": {
223 | "version": "2.1.0",
224 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
225 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
226 | "dev": true,
227 | "requires": {
228 | "restore-cursor": "^2.0.0"
229 | }
230 | },
231 | "cli-width": {
232 | "version": "2.2.0",
233 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
234 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
235 | "dev": true
236 | },
237 | "color-convert": {
238 | "version": "1.9.3",
239 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
240 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
241 | "dev": true,
242 | "requires": {
243 | "color-name": "1.1.3"
244 | }
245 | },
246 | "color-name": {
247 | "version": "1.1.3",
248 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
249 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
250 | "dev": true
251 | },
252 | "commander": {
253 | "version": "2.17.1",
254 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
255 | "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
256 | "dev": true
257 | },
258 | "concat-map": {
259 | "version": "0.0.1",
260 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
261 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
262 | "dev": true
263 | },
264 | "content-disposition": {
265 | "version": "0.5.3",
266 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
267 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
268 | "dev": true,
269 | "requires": {
270 | "safe-buffer": "5.1.2"
271 | }
272 | },
273 | "content-type": {
274 | "version": "1.0.4",
275 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
276 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
277 | "dev": true
278 | },
279 | "cookie": {
280 | "version": "0.4.0",
281 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
282 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
283 | "dev": true
284 | },
285 | "cookie-signature": {
286 | "version": "1.0.6",
287 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
288 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
289 | "dev": true
290 | },
291 | "cross-spawn": {
292 | "version": "6.0.5",
293 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
294 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
295 | "dev": true,
296 | "requires": {
297 | "nice-try": "^1.0.4",
298 | "path-key": "^2.0.1",
299 | "semver": "^5.5.0",
300 | "shebang-command": "^1.2.0",
301 | "which": "^1.2.9"
302 | }
303 | },
304 | "debug": {
305 | "version": "4.1.0",
306 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
307 | "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
308 | "dev": true,
309 | "requires": {
310 | "ms": "^2.1.1"
311 | }
312 | },
313 | "deep-is": {
314 | "version": "0.1.3",
315 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
316 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
317 | "dev": true
318 | },
319 | "del": {
320 | "version": "2.2.2",
321 | "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
322 | "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
323 | "dev": true,
324 | "requires": {
325 | "globby": "^5.0.0",
326 | "is-path-cwd": "^1.0.0",
327 | "is-path-in-cwd": "^1.0.0",
328 | "object-assign": "^4.0.1",
329 | "pify": "^2.0.0",
330 | "pinkie-promise": "^2.0.0",
331 | "rimraf": "^2.2.8"
332 | }
333 | },
334 | "depd": {
335 | "version": "1.1.2",
336 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
337 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
338 | "dev": true
339 | },
340 | "destroy": {
341 | "version": "1.0.4",
342 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
343 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
344 | "dev": true
345 | },
346 | "doctrine": {
347 | "version": "2.1.0",
348 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
349 | "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
350 | "dev": true,
351 | "requires": {
352 | "esutils": "^2.0.2"
353 | }
354 | },
355 | "ee-first": {
356 | "version": "1.1.1",
357 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
358 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
359 | "dev": true
360 | },
361 | "encodeurl": {
362 | "version": "1.0.2",
363 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
364 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
365 | "dev": true
366 | },
367 | "escape-html": {
368 | "version": "1.0.3",
369 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
370 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
371 | "dev": true
372 | },
373 | "escape-string-regexp": {
374 | "version": "1.0.5",
375 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
376 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
377 | "dev": true
378 | },
379 | "eslint": {
380 | "version": "5.6.1",
381 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.1.tgz",
382 | "integrity": "sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA==",
383 | "dev": true,
384 | "requires": {
385 | "@babel/code-frame": "^7.0.0",
386 | "ajv": "^6.5.3",
387 | "chalk": "^2.1.0",
388 | "cross-spawn": "^6.0.5",
389 | "debug": "^4.0.1",
390 | "doctrine": "^2.1.0",
391 | "eslint-scope": "^4.0.0",
392 | "eslint-utils": "^1.3.1",
393 | "eslint-visitor-keys": "^1.0.0",
394 | "espree": "^4.0.0",
395 | "esquery": "^1.0.1",
396 | "esutils": "^2.0.2",
397 | "file-entry-cache": "^2.0.0",
398 | "functional-red-black-tree": "^1.0.1",
399 | "glob": "^7.1.2",
400 | "globals": "^11.7.0",
401 | "ignore": "^4.0.6",
402 | "imurmurhash": "^0.1.4",
403 | "inquirer": "^6.1.0",
404 | "is-resolvable": "^1.1.0",
405 | "js-yaml": "^3.12.0",
406 | "json-stable-stringify-without-jsonify": "^1.0.1",
407 | "levn": "^0.3.0",
408 | "lodash": "^4.17.5",
409 | "minimatch": "^3.0.4",
410 | "mkdirp": "^0.5.1",
411 | "natural-compare": "^1.4.0",
412 | "optionator": "^0.8.2",
413 | "path-is-inside": "^1.0.2",
414 | "pluralize": "^7.0.0",
415 | "progress": "^2.0.0",
416 | "regexpp": "^2.0.0",
417 | "require-uncached": "^1.0.3",
418 | "semver": "^5.5.1",
419 | "strip-ansi": "^4.0.0",
420 | "strip-json-comments": "^2.0.1",
421 | "table": "^4.0.3",
422 | "text-table": "^0.2.0"
423 | }
424 | },
425 | "eslint-scope": {
426 | "version": "4.0.0",
427 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz",
428 | "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==",
429 | "dev": true,
430 | "requires": {
431 | "esrecurse": "^4.1.0",
432 | "estraverse": "^4.1.1"
433 | }
434 | },
435 | "eslint-utils": {
436 | "version": "1.4.2",
437 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
438 | "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
439 | "dev": true,
440 | "requires": {
441 | "eslint-visitor-keys": "^1.0.0"
442 | }
443 | },
444 | "eslint-visitor-keys": {
445 | "version": "1.0.0",
446 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
447 | "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
448 | "dev": true
449 | },
450 | "espree": {
451 | "version": "4.0.0",
452 | "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz",
453 | "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==",
454 | "dev": true,
455 | "requires": {
456 | "acorn": "^5.6.0",
457 | "acorn-jsx": "^4.1.1"
458 | }
459 | },
460 | "esprima": {
461 | "version": "4.0.1",
462 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
463 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
464 | "dev": true
465 | },
466 | "esquery": {
467 | "version": "1.0.1",
468 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
469 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
470 | "dev": true,
471 | "requires": {
472 | "estraverse": "^4.0.0"
473 | }
474 | },
475 | "esrecurse": {
476 | "version": "4.2.1",
477 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
478 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
479 | "dev": true,
480 | "requires": {
481 | "estraverse": "^4.1.0"
482 | }
483 | },
484 | "estraverse": {
485 | "version": "4.2.0",
486 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
487 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
488 | "dev": true
489 | },
490 | "esutils": {
491 | "version": "2.0.2",
492 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
493 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
494 | "dev": true
495 | },
496 | "etag": {
497 | "version": "1.8.1",
498 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
499 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
500 | "dev": true
501 | },
502 | "express": {
503 | "version": "4.17.1",
504 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
505 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
506 | "dev": true,
507 | "requires": {
508 | "accepts": "~1.3.7",
509 | "array-flatten": "1.1.1",
510 | "body-parser": "1.19.0",
511 | "content-disposition": "0.5.3",
512 | "content-type": "~1.0.4",
513 | "cookie": "0.4.0",
514 | "cookie-signature": "1.0.6",
515 | "debug": "2.6.9",
516 | "depd": "~1.1.2",
517 | "encodeurl": "~1.0.2",
518 | "escape-html": "~1.0.3",
519 | "etag": "~1.8.1",
520 | "finalhandler": "~1.1.2",
521 | "fresh": "0.5.2",
522 | "merge-descriptors": "1.0.1",
523 | "methods": "~1.1.2",
524 | "on-finished": "~2.3.0",
525 | "parseurl": "~1.3.3",
526 | "path-to-regexp": "0.1.7",
527 | "proxy-addr": "~2.0.5",
528 | "qs": "6.7.0",
529 | "range-parser": "~1.2.1",
530 | "safe-buffer": "5.1.2",
531 | "send": "0.17.1",
532 | "serve-static": "1.14.1",
533 | "setprototypeof": "1.1.1",
534 | "statuses": "~1.5.0",
535 | "type-is": "~1.6.18",
536 | "utils-merge": "1.0.1",
537 | "vary": "~1.1.2"
538 | },
539 | "dependencies": {
540 | "debug": {
541 | "version": "2.6.9",
542 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
543 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
544 | "dev": true,
545 | "requires": {
546 | "ms": "2.0.0"
547 | }
548 | },
549 | "ms": {
550 | "version": "2.0.0",
551 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
552 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
553 | "dev": true
554 | }
555 | }
556 | },
557 | "external-editor": {
558 | "version": "3.0.3",
559 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
560 | "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
561 | "dev": true,
562 | "requires": {
563 | "chardet": "^0.7.0",
564 | "iconv-lite": "^0.4.24",
565 | "tmp": "^0.0.33"
566 | }
567 | },
568 | "fast-deep-equal": {
569 | "version": "2.0.1",
570 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
571 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
572 | "dev": true
573 | },
574 | "fast-json-stable-stringify": {
575 | "version": "2.0.0",
576 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
577 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
578 | "dev": true
579 | },
580 | "fast-levenshtein": {
581 | "version": "2.0.6",
582 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
583 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
584 | "dev": true
585 | },
586 | "figures": {
587 | "version": "2.0.0",
588 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
589 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
590 | "dev": true,
591 | "requires": {
592 | "escape-string-regexp": "^1.0.5"
593 | }
594 | },
595 | "file-entry-cache": {
596 | "version": "2.0.0",
597 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
598 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
599 | "dev": true,
600 | "requires": {
601 | "flat-cache": "^1.2.1",
602 | "object-assign": "^4.0.1"
603 | }
604 | },
605 | "finalhandler": {
606 | "version": "1.1.2",
607 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
608 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
609 | "dev": true,
610 | "requires": {
611 | "debug": "2.6.9",
612 | "encodeurl": "~1.0.2",
613 | "escape-html": "~1.0.3",
614 | "on-finished": "~2.3.0",
615 | "parseurl": "~1.3.3",
616 | "statuses": "~1.5.0",
617 | "unpipe": "~1.0.0"
618 | },
619 | "dependencies": {
620 | "debug": {
621 | "version": "2.6.9",
622 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
623 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
624 | "dev": true,
625 | "requires": {
626 | "ms": "2.0.0"
627 | }
628 | },
629 | "ms": {
630 | "version": "2.0.0",
631 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
632 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
633 | "dev": true
634 | }
635 | }
636 | },
637 | "flat-cache": {
638 | "version": "1.3.0",
639 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz",
640 | "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=",
641 | "dev": true,
642 | "requires": {
643 | "circular-json": "^0.3.1",
644 | "del": "^2.0.2",
645 | "graceful-fs": "^4.1.2",
646 | "write": "^0.2.1"
647 | }
648 | },
649 | "forwarded": {
650 | "version": "0.1.2",
651 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
652 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
653 | "dev": true
654 | },
655 | "fresh": {
656 | "version": "0.5.2",
657 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
658 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
659 | "dev": true
660 | },
661 | "fs.realpath": {
662 | "version": "1.0.0",
663 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
664 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
665 | "dev": true
666 | },
667 | "functional-red-black-tree": {
668 | "version": "1.0.1",
669 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
670 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
671 | "dev": true
672 | },
673 | "glob": {
674 | "version": "7.1.3",
675 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
676 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
677 | "dev": true,
678 | "requires": {
679 | "fs.realpath": "^1.0.0",
680 | "inflight": "^1.0.4",
681 | "inherits": "2",
682 | "minimatch": "^3.0.4",
683 | "once": "^1.3.0",
684 | "path-is-absolute": "^1.0.0"
685 | }
686 | },
687 | "globals": {
688 | "version": "11.8.0",
689 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz",
690 | "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==",
691 | "dev": true
692 | },
693 | "globby": {
694 | "version": "5.0.0",
695 | "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
696 | "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
697 | "dev": true,
698 | "requires": {
699 | "array-union": "^1.0.1",
700 | "arrify": "^1.0.0",
701 | "glob": "^7.0.3",
702 | "object-assign": "^4.0.1",
703 | "pify": "^2.0.0",
704 | "pinkie-promise": "^2.0.0"
705 | }
706 | },
707 | "graceful-fs": {
708 | "version": "4.1.11",
709 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
710 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
711 | "dev": true
712 | },
713 | "has-flag": {
714 | "version": "3.0.0",
715 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
716 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
717 | "dev": true
718 | },
719 | "http-errors": {
720 | "version": "1.7.2",
721 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
722 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
723 | "dev": true,
724 | "requires": {
725 | "depd": "~1.1.2",
726 | "inherits": "2.0.3",
727 | "setprototypeof": "1.1.1",
728 | "statuses": ">= 1.5.0 < 2",
729 | "toidentifier": "1.0.0"
730 | }
731 | },
732 | "iconv-lite": {
733 | "version": "0.4.24",
734 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
735 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
736 | "dev": true,
737 | "requires": {
738 | "safer-buffer": ">= 2.1.2 < 3"
739 | }
740 | },
741 | "ignore": {
742 | "version": "4.0.6",
743 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
744 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
745 | "dev": true
746 | },
747 | "imurmurhash": {
748 | "version": "0.1.4",
749 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
750 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
751 | "dev": true
752 | },
753 | "inflight": {
754 | "version": "1.0.6",
755 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
756 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
757 | "dev": true,
758 | "requires": {
759 | "once": "^1.3.0",
760 | "wrappy": "1"
761 | }
762 | },
763 | "inherits": {
764 | "version": "2.0.3",
765 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
766 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
767 | "dev": true
768 | },
769 | "inquirer": {
770 | "version": "6.2.0",
771 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz",
772 | "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==",
773 | "dev": true,
774 | "requires": {
775 | "ansi-escapes": "^3.0.0",
776 | "chalk": "^2.0.0",
777 | "cli-cursor": "^2.1.0",
778 | "cli-width": "^2.0.0",
779 | "external-editor": "^3.0.0",
780 | "figures": "^2.0.0",
781 | "lodash": "^4.17.10",
782 | "mute-stream": "0.0.7",
783 | "run-async": "^2.2.0",
784 | "rxjs": "^6.1.0",
785 | "string-width": "^2.1.0",
786 | "strip-ansi": "^4.0.0",
787 | "through": "^2.3.6"
788 | }
789 | },
790 | "ipaddr.js": {
791 | "version": "1.9.1",
792 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
793 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
794 | "dev": true
795 | },
796 | "is-fullwidth-code-point": {
797 | "version": "2.0.0",
798 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
799 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
800 | "dev": true
801 | },
802 | "is-path-cwd": {
803 | "version": "1.0.0",
804 | "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
805 | "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
806 | "dev": true
807 | },
808 | "is-path-in-cwd": {
809 | "version": "1.0.1",
810 | "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
811 | "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
812 | "dev": true,
813 | "requires": {
814 | "is-path-inside": "^1.0.0"
815 | }
816 | },
817 | "is-path-inside": {
818 | "version": "1.0.1",
819 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
820 | "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
821 | "dev": true,
822 | "requires": {
823 | "path-is-inside": "^1.0.1"
824 | }
825 | },
826 | "is-promise": {
827 | "version": "2.1.0",
828 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
829 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
830 | "dev": true
831 | },
832 | "is-resolvable": {
833 | "version": "1.1.0",
834 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
835 | "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
836 | "dev": true
837 | },
838 | "isexe": {
839 | "version": "2.0.0",
840 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
841 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
842 | "dev": true
843 | },
844 | "js-tokens": {
845 | "version": "4.0.0",
846 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
847 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
848 | "dev": true
849 | },
850 | "js-yaml": {
851 | "version": "3.13.1",
852 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
853 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
854 | "dev": true,
855 | "requires": {
856 | "argparse": "^1.0.7",
857 | "esprima": "^4.0.0"
858 | }
859 | },
860 | "json-schema-traverse": {
861 | "version": "0.4.1",
862 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
863 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
864 | "dev": true
865 | },
866 | "json-stable-stringify-without-jsonify": {
867 | "version": "1.0.1",
868 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
869 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
870 | "dev": true
871 | },
872 | "levn": {
873 | "version": "0.3.0",
874 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
875 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
876 | "dev": true,
877 | "requires": {
878 | "prelude-ls": "~1.1.2",
879 | "type-check": "~0.3.2"
880 | }
881 | },
882 | "lodash": {
883 | "version": "4.17.21",
884 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
885 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
886 | "dev": true
887 | },
888 | "media-typer": {
889 | "version": "0.3.0",
890 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
891 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
892 | "dev": true
893 | },
894 | "merge-descriptors": {
895 | "version": "1.0.1",
896 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
897 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
898 | "dev": true
899 | },
900 | "methods": {
901 | "version": "1.1.2",
902 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
903 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
904 | "dev": true
905 | },
906 | "mime": {
907 | "version": "1.6.0",
908 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
909 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
910 | "dev": true
911 | },
912 | "mime-db": {
913 | "version": "1.44.0",
914 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
915 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
916 | "dev": true
917 | },
918 | "mime-types": {
919 | "version": "2.1.27",
920 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
921 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
922 | "dev": true,
923 | "requires": {
924 | "mime-db": "1.44.0"
925 | }
926 | },
927 | "mimic-fn": {
928 | "version": "1.2.0",
929 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
930 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
931 | "dev": true
932 | },
933 | "minimatch": {
934 | "version": "3.0.4",
935 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
936 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
937 | "dev": true,
938 | "requires": {
939 | "brace-expansion": "^1.1.7"
940 | }
941 | },
942 | "minimist": {
943 | "version": "0.0.8",
944 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
945 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
946 | "dev": true
947 | },
948 | "mkdirp": {
949 | "version": "0.5.1",
950 | "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
951 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
952 | "dev": true,
953 | "requires": {
954 | "minimist": "0.0.8"
955 | }
956 | },
957 | "ms": {
958 | "version": "2.1.1",
959 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
960 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
961 | "dev": true
962 | },
963 | "mute-stream": {
964 | "version": "0.0.7",
965 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
966 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
967 | "dev": true
968 | },
969 | "natural-compare": {
970 | "version": "1.4.0",
971 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
972 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
973 | "dev": true
974 | },
975 | "negotiator": {
976 | "version": "0.6.2",
977 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
978 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
979 | "dev": true
980 | },
981 | "nice-try": {
982 | "version": "1.0.5",
983 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
984 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
985 | "dev": true
986 | },
987 | "object-assign": {
988 | "version": "4.1.1",
989 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
990 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
991 | "dev": true
992 | },
993 | "on-finished": {
994 | "version": "2.3.0",
995 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
996 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
997 | "dev": true,
998 | "requires": {
999 | "ee-first": "1.1.1"
1000 | }
1001 | },
1002 | "once": {
1003 | "version": "1.4.0",
1004 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1005 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1006 | "dev": true,
1007 | "requires": {
1008 | "wrappy": "1"
1009 | }
1010 | },
1011 | "onetime": {
1012 | "version": "2.0.1",
1013 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
1014 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
1015 | "dev": true,
1016 | "requires": {
1017 | "mimic-fn": "^1.0.0"
1018 | }
1019 | },
1020 | "optionator": {
1021 | "version": "0.8.2",
1022 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
1023 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
1024 | "dev": true,
1025 | "requires": {
1026 | "deep-is": "~0.1.3",
1027 | "fast-levenshtein": "~2.0.4",
1028 | "levn": "~0.3.0",
1029 | "prelude-ls": "~1.1.2",
1030 | "type-check": "~0.3.2",
1031 | "wordwrap": "~1.0.0"
1032 | }
1033 | },
1034 | "os-tmpdir": {
1035 | "version": "1.0.2",
1036 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
1037 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
1038 | "dev": true
1039 | },
1040 | "parseurl": {
1041 | "version": "1.3.3",
1042 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1043 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1044 | "dev": true
1045 | },
1046 | "path-is-absolute": {
1047 | "version": "1.0.1",
1048 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1049 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
1050 | "dev": true
1051 | },
1052 | "path-is-inside": {
1053 | "version": "1.0.2",
1054 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
1055 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
1056 | "dev": true
1057 | },
1058 | "path-key": {
1059 | "version": "2.0.1",
1060 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
1061 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
1062 | "dev": true
1063 | },
1064 | "path-to-regexp": {
1065 | "version": "0.1.7",
1066 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1067 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
1068 | "dev": true
1069 | },
1070 | "pify": {
1071 | "version": "2.3.0",
1072 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1073 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
1074 | "dev": true
1075 | },
1076 | "pinkie": {
1077 | "version": "2.0.4",
1078 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
1079 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
1080 | "dev": true
1081 | },
1082 | "pinkie-promise": {
1083 | "version": "2.0.1",
1084 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
1085 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
1086 | "dev": true,
1087 | "requires": {
1088 | "pinkie": "^2.0.0"
1089 | }
1090 | },
1091 | "pluralize": {
1092 | "version": "7.0.0",
1093 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
1094 | "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
1095 | "dev": true
1096 | },
1097 | "prelude-ls": {
1098 | "version": "1.1.2",
1099 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
1100 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
1101 | "dev": true
1102 | },
1103 | "progress": {
1104 | "version": "2.0.0",
1105 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
1106 | "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
1107 | "dev": true
1108 | },
1109 | "proxy-addr": {
1110 | "version": "2.0.6",
1111 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
1112 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
1113 | "dev": true,
1114 | "requires": {
1115 | "forwarded": "~0.1.2",
1116 | "ipaddr.js": "1.9.1"
1117 | }
1118 | },
1119 | "punycode": {
1120 | "version": "2.1.1",
1121 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1122 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
1123 | "dev": true
1124 | },
1125 | "qs": {
1126 | "version": "6.7.0",
1127 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
1128 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
1129 | "dev": true
1130 | },
1131 | "range-parser": {
1132 | "version": "1.2.1",
1133 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1134 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1135 | "dev": true
1136 | },
1137 | "raw-body": {
1138 | "version": "2.4.0",
1139 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
1140 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
1141 | "dev": true,
1142 | "requires": {
1143 | "bytes": "3.1.0",
1144 | "http-errors": "1.7.2",
1145 | "iconv-lite": "0.4.24",
1146 | "unpipe": "1.0.0"
1147 | }
1148 | },
1149 | "regexpp": {
1150 | "version": "2.0.1",
1151 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
1152 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
1153 | "dev": true
1154 | },
1155 | "require-uncached": {
1156 | "version": "1.0.3",
1157 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
1158 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
1159 | "dev": true,
1160 | "requires": {
1161 | "caller-path": "^0.1.0",
1162 | "resolve-from": "^1.0.0"
1163 | }
1164 | },
1165 | "resolve-from": {
1166 | "version": "1.0.1",
1167 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
1168 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
1169 | "dev": true
1170 | },
1171 | "restore-cursor": {
1172 | "version": "2.0.0",
1173 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
1174 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
1175 | "dev": true,
1176 | "requires": {
1177 | "onetime": "^2.0.0",
1178 | "signal-exit": "^3.0.2"
1179 | }
1180 | },
1181 | "rimraf": {
1182 | "version": "2.6.2",
1183 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
1184 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
1185 | "dev": true,
1186 | "requires": {
1187 | "glob": "^7.0.5"
1188 | }
1189 | },
1190 | "run-async": {
1191 | "version": "2.3.0",
1192 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
1193 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
1194 | "dev": true,
1195 | "requires": {
1196 | "is-promise": "^2.1.0"
1197 | }
1198 | },
1199 | "rxjs": {
1200 | "version": "6.3.3",
1201 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
1202 | "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==",
1203 | "dev": true,
1204 | "requires": {
1205 | "tslib": "^1.9.0"
1206 | }
1207 | },
1208 | "safe-buffer": {
1209 | "version": "5.1.2",
1210 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1211 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
1212 | "dev": true
1213 | },
1214 | "safer-buffer": {
1215 | "version": "2.1.2",
1216 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1217 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1218 | "dev": true
1219 | },
1220 | "semver": {
1221 | "version": "5.5.1",
1222 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
1223 | "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==",
1224 | "dev": true
1225 | },
1226 | "send": {
1227 | "version": "0.17.1",
1228 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
1229 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
1230 | "dev": true,
1231 | "requires": {
1232 | "debug": "2.6.9",
1233 | "depd": "~1.1.2",
1234 | "destroy": "~1.0.4",
1235 | "encodeurl": "~1.0.2",
1236 | "escape-html": "~1.0.3",
1237 | "etag": "~1.8.1",
1238 | "fresh": "0.5.2",
1239 | "http-errors": "~1.7.2",
1240 | "mime": "1.6.0",
1241 | "ms": "2.1.1",
1242 | "on-finished": "~2.3.0",
1243 | "range-parser": "~1.2.1",
1244 | "statuses": "~1.5.0"
1245 | },
1246 | "dependencies": {
1247 | "debug": {
1248 | "version": "2.6.9",
1249 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1250 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1251 | "dev": true,
1252 | "requires": {
1253 | "ms": "2.0.0"
1254 | },
1255 | "dependencies": {
1256 | "ms": {
1257 | "version": "2.0.0",
1258 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1259 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
1260 | "dev": true
1261 | }
1262 | }
1263 | }
1264 | }
1265 | },
1266 | "serve-static": {
1267 | "version": "1.14.1",
1268 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
1269 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
1270 | "dev": true,
1271 | "requires": {
1272 | "encodeurl": "~1.0.2",
1273 | "escape-html": "~1.0.3",
1274 | "parseurl": "~1.3.3",
1275 | "send": "0.17.1"
1276 | }
1277 | },
1278 | "setprototypeof": {
1279 | "version": "1.1.1",
1280 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
1281 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
1282 | "dev": true
1283 | },
1284 | "shebang-command": {
1285 | "version": "1.2.0",
1286 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
1287 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
1288 | "dev": true,
1289 | "requires": {
1290 | "shebang-regex": "^1.0.0"
1291 | }
1292 | },
1293 | "shebang-regex": {
1294 | "version": "1.0.0",
1295 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
1296 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
1297 | "dev": true
1298 | },
1299 | "signal-exit": {
1300 | "version": "3.0.2",
1301 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
1302 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
1303 | "dev": true
1304 | },
1305 | "slice-ansi": {
1306 | "version": "1.0.0",
1307 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
1308 | "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
1309 | "dev": true,
1310 | "requires": {
1311 | "is-fullwidth-code-point": "^2.0.0"
1312 | }
1313 | },
1314 | "sprintf-js": {
1315 | "version": "1.0.3",
1316 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1317 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
1318 | "dev": true
1319 | },
1320 | "statuses": {
1321 | "version": "1.5.0",
1322 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
1323 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
1324 | "dev": true
1325 | },
1326 | "string-width": {
1327 | "version": "2.1.1",
1328 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
1329 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
1330 | "dev": true,
1331 | "requires": {
1332 | "is-fullwidth-code-point": "^2.0.0",
1333 | "strip-ansi": "^4.0.0"
1334 | }
1335 | },
1336 | "strip-ansi": {
1337 | "version": "4.0.0",
1338 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
1339 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
1340 | "dev": true,
1341 | "requires": {
1342 | "ansi-regex": "^3.0.0"
1343 | }
1344 | },
1345 | "strip-json-comments": {
1346 | "version": "2.0.1",
1347 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
1348 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
1349 | "dev": true
1350 | },
1351 | "supports-color": {
1352 | "version": "5.5.0",
1353 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1354 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1355 | "dev": true,
1356 | "requires": {
1357 | "has-flag": "^3.0.0"
1358 | }
1359 | },
1360 | "table": {
1361 | "version": "4.0.3",
1362 | "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz",
1363 | "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==",
1364 | "dev": true,
1365 | "requires": {
1366 | "ajv": "^6.0.1",
1367 | "ajv-keywords": "^3.0.0",
1368 | "chalk": "^2.1.0",
1369 | "lodash": "^4.17.4",
1370 | "slice-ansi": "1.0.0",
1371 | "string-width": "^2.1.1"
1372 | }
1373 | },
1374 | "text-table": {
1375 | "version": "0.2.0",
1376 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
1377 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
1378 | "dev": true
1379 | },
1380 | "through": {
1381 | "version": "2.3.8",
1382 | "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
1383 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
1384 | "dev": true
1385 | },
1386 | "tmp": {
1387 | "version": "0.0.33",
1388 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
1389 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
1390 | "dev": true,
1391 | "requires": {
1392 | "os-tmpdir": "~1.0.2"
1393 | }
1394 | },
1395 | "toidentifier": {
1396 | "version": "1.0.0",
1397 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
1398 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
1399 | "dev": true
1400 | },
1401 | "tslib": {
1402 | "version": "1.9.3",
1403 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
1404 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
1405 | "dev": true
1406 | },
1407 | "type-check": {
1408 | "version": "0.3.2",
1409 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
1410 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
1411 | "dev": true,
1412 | "requires": {
1413 | "prelude-ls": "~1.1.2"
1414 | }
1415 | },
1416 | "type-is": {
1417 | "version": "1.6.18",
1418 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1419 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1420 | "dev": true,
1421 | "requires": {
1422 | "media-typer": "0.3.0",
1423 | "mime-types": "~2.1.24"
1424 | }
1425 | },
1426 | "uglify-js": {
1427 | "version": "3.4.9",
1428 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
1429 | "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
1430 | "dev": true,
1431 | "requires": {
1432 | "commander": "~2.17.1",
1433 | "source-map": "~0.6.1"
1434 | },
1435 | "dependencies": {
1436 | "source-map": {
1437 | "version": "0.6.1",
1438 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1439 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1440 | "dev": true
1441 | }
1442 | }
1443 | },
1444 | "unpipe": {
1445 | "version": "1.0.0",
1446 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1447 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
1448 | "dev": true
1449 | },
1450 | "uri-js": {
1451 | "version": "4.2.2",
1452 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
1453 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
1454 | "dev": true,
1455 | "requires": {
1456 | "punycode": "^2.1.0"
1457 | }
1458 | },
1459 | "utils-merge": {
1460 | "version": "1.0.1",
1461 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1462 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
1463 | "dev": true
1464 | },
1465 | "vary": {
1466 | "version": "1.1.2",
1467 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1468 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
1469 | "dev": true
1470 | },
1471 | "which": {
1472 | "version": "1.3.1",
1473 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
1474 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
1475 | "dev": true,
1476 | "requires": {
1477 | "isexe": "^2.0.0"
1478 | }
1479 | },
1480 | "wordwrap": {
1481 | "version": "1.0.0",
1482 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
1483 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
1484 | "dev": true
1485 | },
1486 | "wrappy": {
1487 | "version": "1.0.2",
1488 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1489 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1490 | "dev": true
1491 | },
1492 | "write": {
1493 | "version": "0.2.1",
1494 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
1495 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
1496 | "dev": true,
1497 | "requires": {
1498 | "mkdirp": "^0.5.1"
1499 | }
1500 | }
1501 | }
1502 | }
1503 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tunajs",
3 | "version": "1.0.15",
4 | "description": "Audio effects library for the Web Audio API",
5 | "main": "tuna.js",
6 | "directories": {
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "uglify": "uglifyjs tuna.js -o tuna-min.js",
12 | "patch": "npm version patch && npm publish"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/Theodeus/tuna.git"
17 | },
18 | "keywords": [
19 | "web audio",
20 | "audio effects",
21 | "tuna",
22 | "tunajs",
23 | "tuna.js"
24 | ],
25 | "author": "Oskar Eriksson",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/Theodeus/tuna/issues"
29 | },
30 | "homepage": "https://github.com/Theodeus/tuna#readme",
31 | "devDependencies": {
32 | "eslint": "^5.6.1",
33 | "express": "^4.17.1",
34 | "uglify-js": "^3.4.9"
35 | },
36 | "dependencies": {}
37 | }
38 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | # Running tests
2 |
3 | Tests depend on the [Jasmine](http://jasmine.github.io/) testing framework,
4 | currently using v. 2.3.4, and are run in the browser. The Jasmine files are loaded on the fly from the CloudFlare CDN.
5 |
6 | [Express](https://expressjs.com/) is needed for running a basic local server. Run ```npm install express``` to install it. Then run ```node server.js``` in the tests folder to start the server. Finally, navigate to http://localhost:8000 in your browser to run the tests.npm
7 |
8 | To debug the effects audibly, please see [Tuna test](https://github.com/Theodeus/tunatest) - simple GUI to debug and preview the Tuna effects.
--------------------------------------------------------------------------------
/tests/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const app = express();
4 | app.use(express.static(__dirname));
5 | app.use(express.static(path.join(__dirname, '..')));
6 | app.listen(8000, () => console.log('Tuna Test is listening on port 8000!'));
--------------------------------------------------------------------------------
/tests/tests.js:
--------------------------------------------------------------------------------
1 | describe("In Tuna", function() {
2 | var context, tuna;
3 |
4 | beforeAll(function(done) {
5 | //start AudioContext and then tests on button press
6 | document.getElementById("start").addEventListener("click", function() {
7 | context = new AudioContext();
8 | tuna = new Tuna(context);
9 | setTimeout(function checkCurrentTime() {
10 | if (context.currentTime > 0) {
11 | done();
12 | } else {
13 | setTimeout(checkCurrentTime, 100);
14 | }
15 | }, 100);
16 | });
17 | });
18 |
19 | describe("a Bitcrusher node", function() {
20 | var bitcrusher;
21 |
22 | beforeEach(function() {
23 | bitcrusher = new tuna.Bitcrusher();
24 | });
25 |
26 | it("will have default values set", function() {
27 | expect(bitcrusher.bits).toEqual(4);
28 | expect(bitcrusher.normfreq).toBeCloseTo(0.1, 1);
29 | expect(bitcrusher.bufferSize).toEqual(4096);
30 | expect(bitcrusher.bypass).toBeFalsy();
31 | });
32 |
33 | it("will have passed values set", function() {
34 | bitcrusher = new tuna.Bitcrusher({
35 | bits: 5,
36 | normfreq: 0.2,
37 | bufferSize: 2048,
38 | bypass: true
39 | });
40 | expect(bitcrusher.bits).toEqual(5);
41 | expect(bitcrusher.normfreq).toBeCloseTo(0.2, 1);
42 | expect(bitcrusher.bufferSize).toEqual(2048);
43 | expect(bitcrusher.bypass).toBeTruthy();
44 | });
45 |
46 | it("will be activated", function() {
47 | bitcrusher.activateCallback = jasmine.createSpy("activate_bitchrusher");
48 | bitcrusher.bypass = true;
49 | bitcrusher.bypass = false;
50 | expect(bitcrusher.activateCallback).toHaveBeenCalled();
51 | });
52 |
53 | });
54 |
55 | describe("a Cabinet node", function() {
56 | var cabinet;
57 |
58 | beforeEach(function() {
59 | cabinet = new tuna.Cabinet();
60 | });
61 |
62 | it("will will have default values set", function() {
63 | expect(cabinet.makeupGain.value).toEqual(1);
64 | expect(cabinet.bypass).toBeFalsy();
65 | });
66 |
67 | it("will have passed values set", function() {
68 | cabinet = new tuna.Cabinet({
69 | makeupGain: 5.2,
70 | bypass: true
71 | });
72 | expect(cabinet.makeupGain.value).toBeCloseTo(5.2, 1);
73 | expect(cabinet.bypass).toBeTruthy();
74 | });
75 |
76 | it("will be activated", function() {
77 | cabinet.activateCallback = jasmine.createSpy("activate_cabinet");
78 | cabinet.bypass = true;
79 | cabinet.bypass = false;
80 | expect(cabinet.activateCallback).toHaveBeenCalled();
81 | });
82 |
83 | });
84 |
85 | describe("a Chorus node", function() {
86 | var chorus;
87 |
88 | beforeEach(function() {
89 | chorus = new tuna.Chorus();
90 | });
91 |
92 | it("will have default values set", function() {
93 | expect(chorus.rate).toBeCloseTo(1.5, 1);
94 | expect(chorus.feedback).toBeCloseTo(0.4, 1);
95 | expect(chorus.delay).toBeCloseTo(0.0002 * (Math.pow(10, 0.0045) * 2), 5);
96 | expect(chorus.depth).toBeCloseTo(0.7, 1);
97 | expect(chorus.bypass).toBeFalsy();
98 | });
99 |
100 | it("will have passed values set", function() {
101 | chorus = new tuna.Chorus({
102 | rate: 1.0,
103 | feedback: 0.2,
104 | delay: 0.6,
105 | depth: 0.5,
106 | bypass: true
107 | });
108 | expect(chorus.rate).toBeCloseTo(1.0, 1);
109 | expect(chorus.feedback).toBeCloseTo(0.2, 1);
110 | expect(chorus.delay).toBeCloseTo(0.0002 * (Math.pow(10, 0.6) * 2), 5);
111 | expect(chorus.depth).toBeCloseTo(0.5, 1);
112 | expect(chorus.bypass).toBeTruthy();
113 | });
114 |
115 | it("will be activated", function() {
116 | chorus.activateCallback = jasmine.createSpy("activate_chorus");
117 | chorus.bypass = true;
118 | chorus.bypass = false;
119 | expect(chorus.activateCallback).toHaveBeenCalled();
120 | });
121 |
122 | });
123 |
124 | describe("a Compressor node", function() {
125 | var compressor;
126 |
127 | beforeEach(function() {
128 | compressor = new tuna.Compressor();
129 | });
130 |
131 | it("will have default values set", function() {
132 | expect(compressor.threshold.value).toEqual(-20);
133 | expect(compressor.release.value).toEqual(250 / 1000);
134 | expect(compressor.makeupGain.value).toBeCloseTo(1.12, 2);
135 | expect(compressor.attack.value).toBeCloseTo(1 / 1000, 2);
136 | expect(compressor.ratio.value).toEqual(4);
137 | expect(compressor.knee.value).toEqual(5);
138 | expect(compressor.automakeup).toBeFalsy();
139 | expect(compressor.bypass).toBeFalsy();
140 | });
141 |
142 | it("will have passed values set", function() {
143 | compressor = new tuna.Compressor({
144 | threshold: -35,
145 | release: 200,
146 | makeupGain: 2.5,
147 | attack: 2,
148 | ratio: 3,
149 | knee: 4,
150 | bypass: true,
151 | })
152 | expect(compressor.threshold.value).toEqual(-35);
153 | expect(compressor.release.value).toBeCloseTo(200 / 1000, 3);
154 | expect(compressor.makeupGain.value).toBeCloseTo(1.33, 2);
155 | expect(compressor.attack.value).toBeCloseTo(2 / 1000, 3);
156 | expect(compressor.ratio.value).toEqual(3);
157 | expect(compressor.knee.value).toEqual(4);
158 | expect(compressor.bypass).toBeTruthy();
159 |
160 | // automakeup makes threshold, ratio and knee all change makeupGain,
161 | // hence testing it down here
162 | compressor = new tuna.Compressor({
163 | automakeup: true,
164 | });
165 | expect(compressor.automakeup).toBeTruthy();
166 | });
167 |
168 | it("will be activated", function() {
169 | compressor.activateCallback = jasmine.createSpy("activate_compressor");
170 | compressor.bypass = true;
171 | compressor.bypass = false;
172 | expect(compressor.activateCallback).toHaveBeenCalled();
173 | });
174 |
175 | });
176 |
177 | describe("a Delay node", function() {
178 | var delay;
179 |
180 | beforeEach(function() {
181 | delay = new tuna.Delay();
182 | });
183 |
184 | it("will have default values set", function() {
185 | expect(delay.cutoff.value).toBeCloseTo(20000, 1);
186 | expect(delay.delayTime.value).toBeCloseTo(0.1, 1);
187 | expect(delay.feedback.value).toBeCloseTo(0.45, 2);
188 | expect(delay.wetLevel.value).toBeCloseTo(0.5, 1);
189 | expect(delay.dryLevel.value).toBeCloseTo(1, 1);
190 | expect(delay.bypass).toBeFalsy();
191 | });
192 |
193 | it("will have passed values set", function() {
194 | delay = new tuna.Delay({
195 | feedback: 0.95,
196 | delayTime: 500,
197 | wetLevel: 0.256,
198 | dryLevel: 0.6,
199 | cutoff: 2000,
200 | bypass: true
201 | });
202 | expect(delay.cutoff.value).toBeCloseTo(2000, 1);
203 | expect(delay.delayTime.value).toBeCloseTo(0.5, 1);
204 | expect(delay.feedback.value).toBeCloseTo(0.95, 2);
205 | expect(delay.wetLevel.value).toBeCloseTo(0.256, 3);
206 | expect(delay.dryLevel.value).toBeCloseTo(0.6, 1);
207 | expect(delay.bypass).toBeTruthy();
208 | });
209 |
210 | it("will be activated", function() {
211 | delay.activateCallback = jasmine.createSpy("activate_delay");
212 | delay.bypass = true;
213 | delay.bypass = false;
214 | expect(delay.activateCallback).toHaveBeenCalled();
215 | });
216 |
217 | });
218 |
219 | describe("a Convolver node", function() {
220 | var convolver;
221 |
222 | beforeEach(function() {
223 | convolver = new tuna.Convolver();
224 | });
225 |
226 | it("will will have default values set", function() {
227 | expect(convolver.highCut.value).toEqual(22050);
228 | expect(convolver.lowCut.value).toEqual(20);
229 | expect(convolver.wetLevel.value).toEqual(1);
230 | expect(convolver.dryLevel.value).toEqual(1);
231 | expect(convolver.level.value).toEqual(1);
232 | expect(convolver.bypass).toBeFalsy();
233 | });
234 |
235 | it("will have passed values set", function() {
236 | convolver = new tuna.Convolver({
237 | highCut: 2001, //20 to 22050
238 | lowCut: 421, //20 to 22050
239 | dryLevel: 0.4, //0 to 1+
240 | wetLevel: 0.5, //0 to 1+
241 | level: 0.8, //0 to 1+, adjusts total output of both wet and dry
242 | bypass: true
243 | });
244 | expect(convolver.highCut.value).toEqual(2001);
245 | expect(convolver.lowCut.value).toEqual(421);
246 | expect(convolver.wetLevel.value).toBeCloseTo(0.5, 1);
247 | expect(convolver.dryLevel.value).toBeCloseTo(0.4, 1);
248 | expect(convolver.level.value).toBeCloseTo(0.8, 1);
249 | expect(convolver.bypass).toBeTruthy();
250 | });
251 |
252 | it("will be activated", function() {
253 | convolver.activateCallback = jasmine.createSpy("activate_convolver");
254 | convolver.bypass = true;
255 | convolver.bypass = false;
256 | expect(convolver.activateCallback).toHaveBeenCalled();
257 | });
258 |
259 | });
260 |
261 | describe("a EnvelopeFollower node", function() {
262 | var envelope;
263 |
264 | beforeEach(function() {
265 | envelope = new tuna.EnvelopeFollower();
266 | });
267 |
268 | it("will have default values set", function() {
269 | expect(envelope.attackTime).toEqual(0.003);
270 | expect(envelope.releaseTime).toEqual(0.5);
271 | expect(envelope.bypass).toBeFalsy();
272 | });
273 |
274 | it("will have passed values set", function() {
275 | envelope = new tuna.EnvelopeFollower({
276 | attackTime: 0.008,
277 | releaseTime: 0.4,
278 | bypass: true
279 | });
280 | expect(envelope.attackTime).toEqual(0.008);
281 | expect(envelope.releaseTime).toEqual(0.4);
282 | expect(envelope.bypass).toBeTruthy();
283 | });
284 |
285 | it("will be activated", function() {
286 | envelope.activateCallback = jasmine.createSpy();
287 | envelope.bypass = true;
288 | envelope.bypass = false;
289 | expect(envelope.activateCallback).toHaveBeenCalled();
290 | });
291 |
292 | });
293 |
294 | describe("a Filter node", function() {
295 | var filter;
296 |
297 | beforeEach(function() {
298 | filter = new tuna.Filter();
299 | });
300 |
301 | it("will have default values set", function() {
302 | expect(filter.frequency.value).toEqual(800);
303 | expect(filter.Q.value).toEqual(1);
304 | expect(filter.gain.value).toEqual(0);
305 | expect(filter.filterType).toEqual("lowpass");
306 | expect(filter.bypass).toBeFalsy();
307 | });
308 |
309 | it("will have passed values set", function() {
310 | filter = new tuna.Filter({
311 | frequency: 400,
312 | resonance: 2,
313 | gain: 2,
314 | filterType: "highpass",
315 | bypass: true
316 | });
317 | expect(filter.frequency.value).toEqual(400);
318 | expect(filter.Q.value).toEqual(2);
319 | expect(filter.gain.value).toEqual(2);
320 | expect(filter.filterType).toEqual("highpass");
321 | expect(filter.bypass).toBeTruthy();
322 | });
323 |
324 | it("will be activated", function() {
325 | filter.activateCallback = jasmine.createSpy();
326 | filter.bypass = true;
327 | filter.bypass = false;
328 | expect(filter.activateCallback).toHaveBeenCalled();
329 | });
330 |
331 | });
332 |
333 | describe("an LFO node", function() {
334 | var lfo;
335 |
336 | beforeEach(function() {
337 | lfo = new tuna.LFO();
338 | });
339 |
340 | it("will have default values set", function() {
341 | expect(lfo.frequency).toEqual(1);
342 | expect(lfo.offset).toEqual(0.85);
343 | expect(lfo.oscillation).toEqual(0.3);
344 | expect(lfo.phase).toEqual(0);
345 | expect(lfo.bypass).toBeFalsy();
346 | });
347 |
348 | it("will have passed values set", function() {
349 | lfo = new tuna.LFO({
350 | frequency: 0.8,
351 | offset: 0.6,
352 | oscillation: 0.4,
353 | phase: 0.5,
354 | bypass: true
355 | });
356 | expect(lfo.frequency).toEqual(0.8);
357 | expect(lfo.offset).toEqual(0.6);
358 | expect(lfo.oscillation).toEqual(0.4);
359 | expect(lfo.phase).toEqual(0.5);
360 | expect(lfo.bypass).toBeTruthy();
361 | });
362 |
363 | it("will be activated", function() {
364 | lfo.activateCallback = jasmine.createSpy();
365 | lfo.bypass = true;
366 | lfo.bypass = false;
367 | expect(lfo.activateCallback).toHaveBeenCalled();
368 | });
369 |
370 | });
371 |
372 | describe("a MoogFilter node", function() {
373 | var filter;
374 |
375 | beforeEach(function() {
376 | filter = new tuna.MoogFilter();
377 | });
378 |
379 | it("will have default values set", function() {
380 | expect(filter.bufferSize).toEqual(4096);
381 | expect(filter.processor.cutoff).toEqual(0.065);
382 | expect(filter.processor.resonance).toEqual(3.5);
383 | expect(filter.bypass).toBeFalsy();
384 | });
385 |
386 | it("will have passed values set", function() {
387 | filter = new tuna.MoogFilter({
388 | bufferSize: 256,
389 | cutoff: 0.110,
390 | resonance: 2.5,
391 | bypass: true
392 | });
393 | expect(filter.bufferSize).toEqual(256);
394 | expect(filter.processor.cutoff).toEqual(0.110);
395 | expect(filter.processor.resonance).toEqual(2.5);
396 | expect(filter.bypass).toBeTruthy();
397 | });
398 |
399 | it("will be activated", function() {
400 | filter.activateCallback = jasmine.createSpy();
401 | filter.bypass = true;
402 | filter.bypass = false;
403 | expect(filter.activateCallback).toHaveBeenCalled();
404 | });
405 |
406 | });
407 |
408 | describe("a Phaser node", function() {
409 | var phaser;
410 |
411 | beforeEach(function() {
412 | phaser = new tuna.Phaser();
413 | });
414 |
415 | it("will have default values set", function() {
416 | expect(phaser.rate).toEqual(0.1);
417 | expect(phaser.depth).toEqual(0.6);
418 | expect(phaser.feedback).toEqual(0.7);
419 | expect(phaser.stereoPhase).toEqual(40);
420 | expect(phaser.baseModulationFrequency).toEqual(700);
421 | expect(phaser.bypass).toBeFalsy();
422 | });
423 |
424 | it("will have passed values set", function() {
425 | phaser = new tuna.Phaser({
426 | rate: 0.2,
427 | depth: 0.8,
428 | feedback: 0.5,
429 | stereoPhase: 90,
430 | baseModulationFrequency: 550,
431 | bypass: true
432 | });
433 | expect(phaser.rate).toEqual(0.2);
434 | expect(phaser.depth).toEqual(0.8);
435 | expect(phaser.feedback).toEqual(0.5);
436 | expect(phaser.stereoPhase).toEqual(90);
437 | expect(phaser.baseModulationFrequency).toEqual(550);
438 | expect(phaser.bypass).toBeTruthy();
439 | });
440 |
441 | it("will be activated", function() {
442 | phaser.activateCallback = jasmine.createSpy();
443 | phaser.bypass = true;
444 | phaser.bypass = false;
445 | expect(phaser.activateCallback).toHaveBeenCalled();
446 | });
447 |
448 | });
449 |
450 | describe("a PingPongDelay node", function() {
451 | var delay;
452 |
453 | beforeEach(function() {
454 | delay = new tuna.PingPongDelay();
455 | });
456 |
457 | it("will have default values set", function() {
458 | expect(delay.delayTimeLeft).toEqual(200);
459 | expect(delay.delayTimeRight).toEqual(400);
460 | expect(delay.feedbackLevel.gain.value).toBeCloseTo(0.3, 2);
461 | expect(delay.wet.gain.value).toBeCloseTo(0.5, 2);
462 | expect(delay.bypass).toBeFalsy();
463 | });
464 |
465 | it("will have passed values set", function() {
466 | delay = new tuna.PingPongDelay({
467 | delayTimeLeft: 210,
468 | delayTimeRight: 410,
469 | feedback: 0.5,
470 | wetLevel: 0.8,
471 | bypass: true
472 | });
473 | expect(delay.delayTimeLeft).toEqual(210);
474 | expect(delay.delayTimeRight).toEqual(410);
475 | expect(delay.feedbackLevel.gain.value).toBeCloseTo(0.5, 2);
476 | expect(delay.wet.gain.value).toBeCloseTo(0.8, 2);
477 | expect(delay.bypass).toBeTruthy();
478 | });
479 |
480 | it("will be activated", function() {
481 | delay.activateCallback = jasmine.createSpy();
482 | delay.bypass = true;
483 | delay.bypass = false;
484 | expect(delay.activateCallback).toHaveBeenCalled();
485 | });
486 |
487 | });
488 |
489 | describe("a Tremolo node", function() {
490 | var tremolo;
491 |
492 | beforeEach(function() {
493 | tremolo = new tuna.Tremolo();
494 | });
495 |
496 | it("will have default values set", function() {
497 | expect(tremolo.intensity).toEqual(0.3);
498 | expect(tremolo.stereoPhase).toEqual(0);
499 | expect(tremolo.rate).toEqual(5);
500 | expect(tremolo.bypass).toBeFalsy();
501 | });
502 |
503 | it("will have passed values set", function() {
504 | tremolo = new tuna.Tremolo({
505 | intensity: 0.5,
506 | stereoPhase: 90,
507 | rate: 8,
508 | bypass: true
509 | });
510 | expect(tremolo.intensity).toEqual(0.5);
511 | expect(tremolo.stereoPhase).toEqual(90);
512 | expect(tremolo.rate).toEqual(8);
513 | expect(tremolo.bypass).toBeTruthy();
514 | });
515 |
516 | it("will be activated", function() {
517 | tremolo.activateCallback = jasmine.createSpy();
518 | tremolo.bypass = true;
519 | tremolo.bypass = false;
520 | expect(tremolo.activateCallback).toHaveBeenCalled();
521 | });
522 |
523 | });
524 |
525 | describe("a WahWah node", function() {
526 | var wahwah;
527 |
528 | beforeEach(function() {
529 | wahwah = new tuna.WahWah();
530 | });
531 |
532 | it("will have default values set", function() {
533 | expect(wahwah.automode).toBeTruthy();
534 | expect(wahwah.baseFrequency).toEqual(500);
535 | expect(wahwah.excursionOctaves).toEqual(2);
536 | expect(wahwah.sweep).toBeCloseTo(0.0062, 4);
537 | expect(wahwah.resonance).toEqual(10);
538 | expect(wahwah.sensitivity).toBeCloseTo(3.16, 2);
539 | expect(wahwah.bypass).toBeFalsy();
540 | });
541 |
542 | it("will have passed values set", function() {
543 | wahwah = new tuna.WahWah({
544 | automode: false,
545 | baseFrequency: 0.6,
546 | excursionOctaves: 3,
547 | sweep: 0.3,
548 | resonance: 11,
549 | sensitivity: 0.6,
550 | bypass: true
551 | });
552 | expect(wahwah.automode).toBeFalsy();
553 | expect(wahwah.baseFrequency).toBeCloseTo(792.45, 2);
554 | expect(wahwah.excursionOctaves).toEqual(3);
555 | expect(wahwah.sweep).toBeCloseTo(0.0083, 4);
556 | expect(wahwah.resonance).toEqual(11);
557 | expect(wahwah.sensitivity).toBeCloseTo(3.98, 2);
558 | expect(wahwah.bypass).toBeTruthy();
559 | });
560 |
561 | it("will be activated", function() {
562 | wahwah.activateCallback = jasmine.createSpy();
563 | wahwah.bypass = true;
564 | wahwah.bypass = false;
565 | expect(wahwah.activateCallback).toHaveBeenCalled();
566 | });
567 | });
568 |
569 | describe("a Gain node", function() {
570 | var gain;
571 |
572 | beforeEach(function() {
573 | gain = new tuna.Gain();
574 | });
575 |
576 | it("will have default values set", function() {
577 | expect(gain.gain.value).toEqual(1);
578 | expect(gain.bypass).toBeFalsy();
579 | });
580 |
581 | it("will have passed values set", function() {
582 | gain = new tuna.Gain({
583 | gain: 3,
584 | bypass: true
585 | });
586 | expect(gain.gain.value).toEqual(3);
587 | expect(gain.bypass).toBeTruthy();
588 | });
589 |
590 | it("will be activated", function() {
591 | gain.activateCallback = jasmine.createSpy();
592 | gain.bypass = true;
593 | gain.bypass = false;
594 | expect(gain.activateCallback).toHaveBeenCalled();
595 | });
596 | });
597 |
598 | describe("a Panner node", function() {
599 | var panner;
600 |
601 | beforeEach(function() {
602 | panner = new tuna.Panner();
603 | });
604 |
605 | it("will have default values set", function() {
606 | expect(panner.pan.value).toEqual(0);
607 | expect(panner.bypass).toBeFalsy();
608 | });
609 |
610 | it("will have passed values set", function() {
611 | panner = new tuna.Panner({
612 | pan: 0.75,
613 | bypass: true
614 | });
615 | expect(panner.pan.value).toEqual(0.75);
616 | expect(panner.bypass).toBeTruthy();
617 | });
618 |
619 | it("will be activated", function() {
620 | panner.activateCallback = jasmine.createSpy();
621 | panner.bypass = true;
622 | panner.bypass = false;
623 | expect(panner.activateCallback).toHaveBeenCalled();
624 | });
625 |
626 | });
627 |
628 | });
629 |
630 |
--------------------------------------------------------------------------------
/tuna-min.js:
--------------------------------------------------------------------------------
1 | (function(){var userContext,userInstance,pipe=function(param,val){param.value=val},Super=Object.create(null,{activate:{writable:true,value:function(doActivate){if(doActivate){this.input.disconnect();this.input.connect(this.activateNode);if(this.activateCallback){this.activateCallback(doActivate)}}else{this.input.disconnect();this.input.connect(this.output)}}},bypass:{get:function(){return this._bypass},set:function(value){if(this._lastBypassValue===value){return}this._bypass=value;this.activate(!value);this._lastBypassValue=value}},connect:{value:function(target){this.output.connect(target)}},disconnect:{value:function(target){this.output.disconnect(target)}},connectInOrder:{value:function(nodeArray){var i=nodeArray.length-1;while(i--){if(!nodeArray[i].connect){return console.error("AudioNode.connectInOrder: TypeError: Not an AudioNode.",nodeArray[i])}if(nodeArray[i+1].input){nodeArray[i].connect(nodeArray[i+1].input)}else{nodeArray[i].connect(nodeArray[i+1])}}}},getDefaults:{value:function(){var result={};for(var key in this.defaults){result[key]=this.defaults[key].value}return result}},automate:{value:function(property,value,duration,startTime){var start=startTime?~~(startTime/1e3):userContext.currentTime,dur=duration?~~(duration/1e3):0,_is=this.defaults[property],param=this[property],method;if(param){if(_is.automatable){if(!duration){method="setValueAtTime"}else{method="linearRampToValueAtTime";param.cancelScheduledValues(start);param.setValueAtTime(param.value,start)}param[method](value,dur+start)}else{param=value}}else{console.error("Invalid Property for "+this.name)}}}}),FLOAT="float",BOOLEAN="boolean",STRING="string",INT="int";if(typeof module!=="undefined"&&module.exports){module.exports=Tuna}else if(typeof define==="function"){window.define("Tuna",definition)}else{window.Tuna=Tuna}function definition(){return Tuna}function Tuna(context){if(!(this instanceof Tuna)){return new Tuna(context)}var _window=typeof window==="undefined"?{}:window;if(!_window.AudioContext){_window.AudioContext=_window.webkitAudioContext}if(!context){console.log("tuna.js: Missing audio context! Creating a new context for you.");context=_window.AudioContext&&new _window.AudioContext}if(!context){throw new Error("Tuna cannot initialize because this environment does not support web audio.")}connectify(context);userContext=context;userInstance=this}function connectify(context){if(context.__connectified__===true)return;var gain=context.createGain(),proto=Object.getPrototypeOf(Object.getPrototypeOf(gain)),oconnect=proto.connect;proto.connect=shimConnect;context.__connectified__=true;function shimConnect(){var node=arguments[0];arguments[0]=Super.isPrototypeOf?Super.isPrototypeOf(node)?node.input:node:node.input||node;oconnect.apply(this,arguments);return node}}function dbToWAVolume(db){return Math.max(0,Math.round(100*Math.pow(2,db/6))/100)}function fmod(x,y){var tmp,tmp2,p=0,pY=0,l=0,l2=0;tmp=x.toExponential().match(/^.\.?(.*)e(.+)$/);p=parseInt(tmp[2],10)-(tmp[1]+"").length;tmp=y.toExponential().match(/^.\.?(.*)e(.+)$/);pY=parseInt(tmp[2],10)-(tmp[1]+"").length;if(pY>p){p=pY}tmp2=x%y;if(p<-100||p>20){l=Math.round(Math.log(tmp2)/Math.log(10));l2=Math.pow(10,l);return(tmp2/l2).toFixed(l-p)*l2}else{return parseFloat(tmp2.toFixed(-p))}}function sign(x){if(x===0){return 1}else{return Math.abs(x)/x}}function tanh(n){return(Math.exp(n)-Math.exp(-n))/(Math.exp(n)+Math.exp(-n))}function initValue(userVal,defaultVal){return userVal===undefined?defaultVal:userVal}Tuna.prototype.Bitcrusher=function(properties){if(!properties){properties=this.getDefaults()}this.bufferSize=properties.bufferSize||this.defaults.bufferSize.value;this.input=userContext.createGain();this.activateNode=userContext.createGain();this.processor=userContext.createScriptProcessor(this.bufferSize,1,1);this.output=userContext.createGain();this.activateNode.connect(this.processor);this.processor.connect(this.output);var phaser=0,last=0,input,output,step,i,length;this.processor.onaudioprocess=function(e){input=e.inputBuffer.getChannelData(0),output=e.outputBuffer.getChannelData(0),step=Math.pow(1/2,this.bits);length=input.length;for(i=0;i=1){phaser-=1;last=step*Math.floor(input[i]/step+.5)}output[i]=last}};this.bits=properties.bits||this.defaults.bits.value;this.normfreq=initValue(properties.normfreq,this.defaults.normfreq.value);this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Bitcrusher.prototype=Object.create(Super,{name:{value:"Bitcrusher"},defaults:{writable:true,value:{bits:{value:4,min:1,max:16,automatable:false,type:INT},bufferSize:{value:4096,min:256,max:16384,automatable:false,type:INT},bypass:{value:false,automatable:false,type:BOOLEAN},normfreq:{value:.1,min:1e-4,max:1,automatable:false,type:FLOAT}}},bits:{enumerable:true,get:function(){return this.processor.bits},set:function(value){this.processor.bits=value}},normfreq:{enumerable:true,get:function(){return this.processor.normfreq},set:function(value){this.processor.normfreq=value}}});Tuna.prototype.Cabinet=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.activateNode=userContext.createGain();this.convolver=this.newConvolver(properties.impulsePath||"../impulses/impulse_guitar.wav");this.makeupNode=userContext.createGain();this.output=userContext.createGain();this.activateNode.connect(this.convolver.input);this.convolver.output.connect(this.makeupNode);this.makeupNode.connect(this.output);this.makeupNode.gain.value=initValue(properties.makeupGain,this.defaults.makeupGain.value);this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Cabinet.prototype=Object.create(Super,{name:{value:"Cabinet"},defaults:{writable:true,value:{makeupGain:{value:1,min:0,max:20,automatable:true,type:FLOAT},bypass:{value:false,automatable:false,type:BOOLEAN}}},makeupGain:{enumerable:true,get:function(){return this.makeupNode.gain},set:function(value){this.makeupNode.gain.setTargetAtTime(value,userContext.currentTime,.01)}},newConvolver:{value:function(impulsePath){return new userInstance.Convolver({impulse:impulsePath,dryLevel:0,wetLevel:1})}}});Tuna.prototype.Chorus=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.attenuator=this.activateNode=userContext.createGain();this.splitter=userContext.createChannelSplitter(2);this.delayL=userContext.createDelay();this.delayR=userContext.createDelay();this.feedbackGainNodeLR=userContext.createGain();this.feedbackGainNodeRL=userContext.createGain();this.merger=userContext.createChannelMerger(2);this.output=userContext.createGain();this.lfoL=new userInstance.LFO({target:this.delayL.delayTime,callback:pipe});this.lfoR=new userInstance.LFO({target:this.delayR.delayTime,callback:pipe});this.input.connect(this.attenuator);this.attenuator.connect(this.output);this.attenuator.connect(this.splitter);this.splitter.connect(this.delayL,0);this.splitter.connect(this.delayR,1);this.delayL.connect(this.feedbackGainNodeLR);this.delayR.connect(this.feedbackGainNodeRL);this.feedbackGainNodeLR.connect(this.delayR);this.feedbackGainNodeRL.connect(this.delayL);this.delayL.connect(this.merger,0,0);this.delayR.connect(this.merger,0,1);this.merger.connect(this.output);this.feedback=initValue(properties.feedback,this.defaults.feedback.value);this.rate=initValue(properties.rate,this.defaults.rate.value);this.delay=initValue(properties.delay,this.defaults.delay.value);this.depth=initValue(properties.depth,this.defaults.depth.value);this.lfoR.phase=Math.PI/2;this.attenuator.gain.value=.6934;this.lfoL.activate(true);this.lfoR.activate(true);this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Chorus.prototype=Object.create(Super,{name:{value:"Chorus"},defaults:{writable:true,value:{feedback:{value:.4,min:0,max:.95,automatable:false,type:FLOAT},delay:{value:.0045,min:0,max:1,automatable:false,type:FLOAT},depth:{value:.7,min:0,max:1,automatable:false,type:FLOAT},rate:{value:1.5,min:0,max:8,automatable:false,type:FLOAT},bypass:{value:false,automatable:false,type:BOOLEAN}}},delay:{enumerable:true,get:function(){return this._delay},set:function(value){this._delay=2e-4*(Math.pow(10,value)*2);this.lfoL.offset=this._delay;this.lfoR.offset=this._delay;this._depth=this._depth}},depth:{enumerable:true,get:function(){return this._depth},set:function(value){this._depth=value;this.lfoL.oscillation=this._depth*this._delay;this.lfoR.oscillation=this._depth*this._delay}},feedback:{enumerable:true,get:function(){return this._feedback},set:function(value){this._feedback=value;this.feedbackGainNodeLR.gain.setTargetAtTime(this._feedback,userContext.currentTime,.01);this.feedbackGainNodeRL.gain.setTargetAtTime(this._feedback,userContext.currentTime,.01)}},rate:{enumerable:true,get:function(){return this._rate},set:function(value){this._rate=value;this.lfoL.frequency=this._rate;this.lfoR.frequency=this._rate}}});Tuna.prototype.Compressor=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.compNode=this.activateNode=userContext.createDynamicsCompressor();this.makeupNode=userContext.createGain();this.output=userContext.createGain();this.compNode.connect(this.makeupNode);this.makeupNode.connect(this.output);this.automakeup=initValue(properties.automakeup,this.defaults.automakeup.value);if(this.automakeup){this.makeupNode.gain.value=dbToWAVolume(this.computeMakeup())}else{this.makeupNode.gain.value=dbToWAVolume(initValue(properties.makeupGain,this.defaults.makeupGain.value))}this.threshold=initValue(properties.threshold,this.defaults.threshold.value);this.release=initValue(properties.release,this.defaults.release.value);this.attack=initValue(properties.attack,this.defaults.attack.value);this.ratio=properties.ratio||this.defaults.ratio.value;this.knee=initValue(properties.knee,this.defaults.knee.value);this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Compressor.prototype=Object.create(Super,{name:{value:"Compressor"},defaults:{writable:true,value:{threshold:{value:-20,min:-60,max:0,automatable:true,type:FLOAT},release:{value:250,min:10,max:2e3,automatable:true,type:FLOAT},makeupGain:{value:1,min:1,max:100,automatable:true,type:FLOAT},attack:{value:1,min:0,max:1e3,automatable:true,type:FLOAT},ratio:{value:4,min:1,max:50,automatable:true,type:FLOAT},knee:{value:5,min:0,max:40,automatable:true,type:FLOAT},automakeup:{value:false,automatable:false,type:BOOLEAN},bypass:{value:false,automatable:false,type:BOOLEAN}}},computeMakeup:{value:function(){var magicCoefficient=4,c=this.compNode;return-(c.threshold.value-c.threshold.value/c.ratio.value)/magicCoefficient}},automakeup:{enumerable:true,get:function(){return this._automakeup},set:function(value){this._automakeup=value;if(this._automakeup)this.makeupGain=this.computeMakeup()}},threshold:{enumerable:true,get:function(){return this.compNode.threshold},set:function(value){this.compNode.threshold.value=value;if(this._automakeup)this.makeupGain=this.computeMakeup()}},ratio:{enumerable:true,get:function(){return this.compNode.ratio},set:function(value){this.compNode.ratio.value=value;if(this._automakeup)this.makeupGain=this.computeMakeup()}},knee:{enumerable:true,get:function(){return this.compNode.knee},set:function(value){this.compNode.knee.value=value;if(this._automakeup)this.makeupGain=this.computeMakeup()}},attack:{enumerable:true,get:function(){return this.compNode.attack},set:function(value){this.compNode.attack.value=value/1e3}},release:{enumerable:true,get:function(){return this.compNode.release},set:function(value){this.compNode.release.value=value/1e3}},makeupGain:{enumerable:true,get:function(){return this.makeupNode.gain},set:function(value){this.makeupNode.gain.setTargetAtTime(dbToWAVolume(value),userContext.currentTime,.01)}}});Tuna.prototype.Convolver=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.activateNode=userContext.createGain();this.convolver=userContext.createConvolver();this.dry=userContext.createGain();this.filterLow=userContext.createBiquadFilter();this.filterHigh=userContext.createBiquadFilter();this.wet=userContext.createGain();this.output=userContext.createGain();this.activateNode.connect(this.filterLow);this.activateNode.connect(this.dry);this.filterLow.connect(this.filterHigh);this.filterHigh.connect(this.convolver);this.convolver.connect(this.wet);this.wet.connect(this.output);this.dry.connect(this.output);this.dry.gain.value=initValue(properties.dryLevel,this.defaults.dryLevel.value);this.wet.gain.value=initValue(properties.wetLevel,this.defaults.wetLevel.value);this.filterHigh.frequency.value=properties.highCut||this.defaults.highCut.value;this.filterLow.frequency.value=properties.lowCut||this.defaults.lowCut.value;this.output.gain.value=initValue(properties.level,this.defaults.level.value);this.filterHigh.type="lowpass";this.filterLow.type="highpass";this.buffer=properties.impulse||"../impulses/ir_rev_short.wav";this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Convolver.prototype=Object.create(Super,{name:{value:"Convolver"},defaults:{writable:true,value:{highCut:{value:22050,min:20,max:22050,automatable:true,type:FLOAT},lowCut:{value:20,min:20,max:22050,automatable:true,type:FLOAT},dryLevel:{value:1,min:0,max:1,automatable:true,type:FLOAT},wetLevel:{value:1,min:0,max:1,automatable:true,type:FLOAT},level:{value:1,min:0,max:1,automatable:true,type:FLOAT},bypass:{value:false,automatable:false,type:BOOLEAN}}},lowCut:{get:function(){return this.filterLow.frequency},set:function(value){this.filterLow.frequency.setTargetAtTime(value,userContext.currentTime,.01)}},highCut:{get:function(){return this.filterHigh.frequency},set:function(value){this.filterHigh.frequency.setTargetAtTime(value,userContext.currentTime,.01)}},level:{get:function(){return this.output.gain},set:function(value){this.output.gain.setTargetAtTime(value,userContext.currentTime,.01)}},dryLevel:{get:function(){return this.dry.gain},set:function(value){this.dry.gain.setTargetAtTime(value,userContext.currentTime,.01)}},wetLevel:{get:function(){return this.wet.gain},set:function(value){this.wet.gain.setTargetAtTime(value,userContext.currentTime,.01)}},buffer:{enumerable:false,get:function(){return this.convolver.buffer},set:function(impulse){var convolver=this.convolver,xhr=new XMLHttpRequest;if(!impulse){console.log("Tuna.Convolver.setBuffer: Missing impulse path!");return}xhr.open("GET",impulse,true);xhr.responseType="arraybuffer";xhr.onreadystatechange=function(){if(xhr.readyState===4){if(xhr.status<300&&xhr.status>199||xhr.status===302){userContext.decodeAudioData(xhr.response,function(buffer){convolver.buffer=buffer},function(e){if(e)console.log("Tuna.Convolver.setBuffer: Error decoding data"+e)})}}};xhr.send(null)}}});Tuna.prototype.Delay=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.activateNode=userContext.createGain();this.dry=userContext.createGain();this.wet=userContext.createGain();this.filter=userContext.createBiquadFilter();this.delay=userContext.createDelay(10);this.feedbackNode=userContext.createGain();this.output=userContext.createGain();this.activateNode.connect(this.delay);this.activateNode.connect(this.dry);this.delay.connect(this.filter);this.filter.connect(this.feedbackNode);this.feedbackNode.connect(this.delay);this.feedbackNode.connect(this.wet);this.wet.connect(this.output);this.dry.connect(this.output);this.delayTime=properties.delayTime||this.defaults.delayTime.value;this.feedbackNode.gain.value=initValue(properties.feedback,this.defaults.feedback.value);this.wet.gain.value=initValue(properties.wetLevel,this.defaults.wetLevel.value);this.dry.gain.value=initValue(properties.dryLevel,this.defaults.dryLevel.value);this.filter.frequency.value=properties.cutoff||this.defaults.cutoff.value;this.filter.type="lowpass";this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Delay.prototype=Object.create(Super,{name:{value:"Delay"},defaults:{writable:true,value:{delayTime:{value:100,min:20,max:1e3,automatable:false,type:FLOAT},feedback:{value:.45,min:0,max:.9,automatable:true,type:FLOAT},cutoff:{value:2e4,min:20,max:2e4,automatable:true,type:FLOAT},wetLevel:{value:.5,min:0,max:1,automatable:true,type:FLOAT},dryLevel:{value:1,min:0,max:1,automatable:true,type:FLOAT},bypass:{value:false,automatable:false,type:BOOLEAN}}},delayTime:{enumerable:true,get:function(){return this.delay.delayTime},set:function(value){this.delay.delayTime.value=value/1e3}},wetLevel:{enumerable:true,get:function(){return this.wet.gain},set:function(value){this.wet.gain.setTargetAtTime(value,userContext.currentTime,.01)}},dryLevel:{enumerable:true,get:function(){return this.dry.gain},set:function(value){this.dry.gain.setTargetAtTime(value,userContext.currentTime,.01)}},feedback:{enumerable:true,get:function(){return this.feedbackNode.gain},set:function(value){this.feedbackNode.gain.setTargetAtTime(value,userContext.currentTime,.01)}},cutoff:{enumerable:true,get:function(){return this.filter.frequency},set:function(value){this.filter.frequency.setTargetAtTime(value,userContext.currentTime,.01)}}});Tuna.prototype.Filter=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.activateNode=userContext.createGain();this.filter=userContext.createBiquadFilter();this.output=userContext.createGain();this.activateNode.connect(this.filter);this.filter.connect(this.output);this.filter.frequency.value=properties.frequency||this.defaults.frequency.value;this.Q=properties.resonance||this.defaults.Q.value;this.filterType=initValue(properties.filterType,this.defaults.filterType.value);this.filter.gain.value=initValue(properties.gain,this.defaults.gain.value);this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Filter.prototype=Object.create(Super,{name:{value:"Filter"},defaults:{writable:true,value:{frequency:{value:800,min:20,max:22050,automatable:true,type:FLOAT},Q:{value:1,min:.001,max:100,automatable:true,type:FLOAT},gain:{value:0,min:-40,max:40,automatable:true,type:FLOAT},bypass:{value:false,automatable:false,type:BOOLEAN},filterType:{value:"lowpass",automatable:false,type:STRING}}},filterType:{enumerable:true,get:function(){return this.filter.type},set:function(value){this.filter.type=value}},Q:{enumerable:true,get:function(){return this.filter.Q},set:function(value){this.filter.Q.value=value}},gain:{enumerable:true,get:function(){return this.filter.gain},set:function(value){this.filter.gain.setTargetAtTime(value,userContext.currentTime,.01)}},frequency:{enumerable:true,get:function(){return this.filter.frequency},set:function(value){this.filter.frequency.setTargetAtTime(value,userContext.currentTime,.01)}}});Tuna.prototype.Gain=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.activateNode=userContext.createGain();this.gainNode=userContext.createGain();this.output=userContext.createGain();this.activateNode.connect(this.gainNode);this.gainNode.connect(this.output);this.gainNode.gain.value=initValue(properties.gain,this.defaults.gain.value);this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.Gain.prototype=Object.create(Super,{name:{value:"Gain"},defaults:{writable:true,value:{bypass:{value:false,automatable:false,type:BOOLEAN},gain:{value:1,automatable:true,type:FLOAT}}},gain:{enumerable:true,get:function(){return this.gainNode.gain},set:function(value){this.gainNode.gain.setTargetAtTime(value,userContext.currentTime,.01)}}});Tuna.prototype.MoogFilter=function(properties){if(!properties){properties=this.getDefaults()}this.bufferSize=properties.bufferSize||this.defaults.bufferSize.value;this.input=userContext.createGain();this.activateNode=userContext.createGain();this.processor=userContext.createScriptProcessor(this.bufferSize,1,1);this.output=userContext.createGain();this.activateNode.connect(this.processor);this.processor.connect(this.output);var in1,in2,in3,in4,out1,out2,out3,out4;in1=in2=in3=in4=out1=out2=out3=out4=0;var input,output,f,fb,i,length,inputFactor;this.processor.onaudioprocess=function(e){input=e.inputBuffer.getChannelData(0);output=e.outputBuffer.getChannelData(0);f=this.cutoff*1.16;inputFactor=.35013*(f*f)*(f*f);fb=this.resonance*(1-.15*f*f);length=input.length;for(i=0;i=0?5.8:1.2);ws_table[i]=tanh(y)}},function(amount,n_samples,ws_table){var i,x,y,a=1-amount;for(i=0;i.99?.99:1-amount;for(i=0;ia){y=a+(abx-a)/(1+Math.pow((abx-a)/(1-a),2))}else if(abx>1){y=abx}ws_table[i]=sign(x)*y*(1/((a+1)/2))}},function(amount,n_samples,ws_table){var i,x;for(i=0;i=-.08905&&x<.320018){ws_table[i]=-6.153*(x*x)+3.9375*x}else{ws_table[i]=.630035}}},function(amount,n_samples,ws_table){var a=2+Math.round(amount*14),bits=Math.round(Math.pow(2,a-1)),i,x;for(i=0;i1?1:value<0?0:value,this._sensitivity);this.setFilterFreq()}},baseFrequency:{enumerable:true,get:function(){return this._baseFrequency},set:function(value){this._baseFrequency=50*Math.pow(10,value*2);this._excursionFrequency=Math.min(userContext.sampleRate/2,this.baseFrequency*Math.pow(2,this._excursionOctaves));this.setFilterFreq()}},excursionOctaves:{enumerable:true,get:function(){return this._excursionOctaves},set:function(value){this._excursionOctaves=value;this._excursionFrequency=Math.min(userContext.sampleRate/2,this.baseFrequency*Math.pow(2,this._excursionOctaves));this.setFilterFreq()}},sensitivity:{enumerable:true,get:function(){return this._sensitivity},set:function(value){this._sensitivity=Math.pow(10,value)}},resonance:{enumerable:true,get:function(){return this._resonance},set:function(value){this._resonance=value;this.filterPeaking.Q.value=this._resonance}},init:{value:function(){this.output.gain.value=1;this.filterPeaking.type="peaking";this.filterBp.type="bandpass";this.filterPeaking.frequency.value=100;this.filterPeaking.gain.value=20;this.filterPeaking.Q.value=5;this.filterBp.frequency.value=100;this.filterBp.Q.value=1}}});Tuna.prototype.EnvelopeFollower=function(properties){if(!properties){properties=this.getDefaults()}this.input=userContext.createGain();this.jsNode=this.output=userContext.createScriptProcessor(this.buffersize,1,1);this.input.connect(this.output);this.attackTime=initValue(properties.attackTime,this.defaults.attackTime.value);this.releaseTime=initValue(properties.releaseTime,this.defaults.releaseTime.value);this._envelope=0;this.target=properties.target||{};this.callback=properties.callback||function(){};this.bypass=properties.bypass||this.defaults.bypass.value};Tuna.prototype.EnvelopeFollower.prototype=Object.create(Super,{name:{value:"EnvelopeFollower"},defaults:{value:{attackTime:{value:.003,min:0,max:.5,automatable:false,type:FLOAT},releaseTime:{value:.5,min:0,max:.5,automatable:false,type:FLOAT},bypass:{value:false,automatable:false,type:BOOLEAN}}},buffersize:{value:256},envelope:{value:0},sampleRate:{value:44100},attackTime:{enumerable:true,get:function(){return this._attackTime},set:function(value){this._attackTime=value;this._attackC=Math.exp(-1/this._attackTime*this.sampleRate/this.buffersize)}},releaseTime:{enumerable:true,get:function(){return this._releaseTime},set:function(value){this._releaseTime=value;this._releaseC=Math.exp(-1/this._releaseTime*this.sampleRate/this.buffersize)}},callback:{get:function(){return this._callback},set:function(value){if(typeof value==="function"){this._callback=value}else{console.error("tuna.js: "+this.name+": Callback must be a function!")}}},target:{get:function(){return this._target},set:function(value){this._target=value}},activate:{value:function(doActivate){this.activated=doActivate;if(doActivate){this.jsNode.connect(userContext.destination);this.jsNode.onaudioprocess=this.returnCompute(this)}else{this.jsNode.disconnect();this.jsNode.onaudioprocess=null}if(this.activateCallback){this.activateCallback(doActivate)}}},returnCompute:{value:function(instance){return function(event){instance.compute(event)}}},compute:{value:function(event){var count=event.inputBuffer.getChannelData(0).length,channels=event.inputBuffer.numberOfChannels,current,chan,rms,i;chan=rms=i=0;for(chan=0;chan2*Math.PI){that._phase=0}callback(that._target,that._offset+that._oscillation*Math.sin(that._phase))}}}});Tuna.toString=Tuna.prototype.toString=function(){return"Please visit https://github.com/Theodeus/tuna/wiki for instructions on how to use Tuna.js"}})();
--------------------------------------------------------------------------------
/tuna.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2012 DinahMoe AB & Oskar Eriksson
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 | /*global module*/
17 | (function() {
18 |
19 | var userContext,
20 | userInstance,
21 | pipe = function(param, val) {
22 | param.value = val;
23 | },
24 | Super = Object.create(null, {
25 | activate: {
26 | writable: true,
27 | value: function(doActivate) {
28 | if (doActivate) {
29 | this.input.disconnect();
30 | this.input.connect(this.activateNode);
31 | if (this.activateCallback) {
32 | this.activateCallback(doActivate);
33 | }
34 | } else {
35 | this.input.disconnect();
36 | this.input.connect(this.output);
37 | }
38 | }
39 | },
40 | bypass: {
41 | get: function() {
42 | return this._bypass;
43 | },
44 | set: function(value) {
45 | if (this._lastBypassValue === value) {
46 | return;
47 | }
48 | this._bypass = value;
49 | this.activate(!value);
50 | this._lastBypassValue = value;
51 | }
52 | },
53 | connect: {
54 | value: function(target) {
55 | this.output.connect(target);
56 | }
57 | },
58 | disconnect: {
59 | value: function(target) {
60 | this.output.disconnect(target);
61 | }
62 | },
63 | connectInOrder: {
64 | value: function(nodeArray) {
65 | var i = nodeArray.length - 1;
66 | while (i--) {
67 | if (!nodeArray[i].connect) {
68 | return console.error("AudioNode.connectInOrder: TypeError: Not an AudioNode.", nodeArray[i]);
69 | }
70 | if (nodeArray[i + 1].input) {
71 | nodeArray[i].connect(nodeArray[i + 1].input);
72 | } else {
73 | nodeArray[i].connect(nodeArray[i + 1]);
74 | }
75 | }
76 | }
77 | },
78 | getDefaults: {
79 | value: function() {
80 | var result = {};
81 | for (var key in this.defaults) {
82 | result[key] = this.defaults[key].value;
83 | }
84 | return result;
85 | }
86 | },
87 | automate: {
88 | value: function(property, value, duration, startTime) {
89 | var start = startTime ? ~~(startTime / 1000) : userContext.currentTime,
90 | dur = duration ? ~~(duration / 1000) : 0,
91 | _is = this.defaults[property],
92 | param = this[property],
93 | method;
94 |
95 | if (param) {
96 | if (_is.automatable) {
97 | if (!duration) {
98 | method = "setValueAtTime";
99 | } else {
100 | method = "linearRampToValueAtTime";
101 | param.cancelScheduledValues(start);
102 | param.setValueAtTime(param.value, start);
103 | }
104 | param[method](value, dur + start);
105 | } else {
106 | param = value;
107 | }
108 | } else {
109 | console.error("Invalid Property for " + this.name);
110 | }
111 | }
112 | }
113 | }),
114 | FLOAT = "float",
115 | BOOLEAN = "boolean",
116 | STRING = "string",
117 | INT = "int";
118 |
119 | if (typeof module !== "undefined" && module.exports) {
120 | module.exports = Tuna;
121 | } else if (typeof define === "function") {
122 | window.define("Tuna", definition);
123 | } else {
124 | window.Tuna = Tuna;
125 | }
126 |
127 | function definition() {
128 | return Tuna;
129 | }
130 |
131 | function Tuna(context) {
132 | if (!(this instanceof Tuna)) {
133 | return new Tuna(context);
134 | }
135 |
136 | var _window = typeof window === "undefined" ? {} : window;
137 |
138 | if (!_window.AudioContext) {
139 | _window.AudioContext = _window.webkitAudioContext;
140 | }
141 | if (!context) {
142 | console.log("tuna.js: Missing audio context! Creating a new context for you.");
143 | context = _window.AudioContext && (new _window.AudioContext());
144 | }
145 | if (!context) {
146 | throw new Error("Tuna cannot initialize because this environment does not support web audio.");
147 | }
148 | connectify(context);
149 | userContext = context;
150 | userInstance = this;
151 | }
152 |
153 | function connectify(context) {
154 | if (context.__connectified__ === true) return;
155 |
156 | var gain = context.createGain(),
157 | proto = Object.getPrototypeOf(Object.getPrototypeOf(gain)),
158 | oconnect = proto.connect;
159 |
160 | proto.connect = shimConnect;
161 | context.__connectified__ = true; // Prevent overriding connect more than once
162 |
163 | function shimConnect() {
164 | var node = arguments[0];
165 | arguments[0] = Super.isPrototypeOf ? (Super.isPrototypeOf(node) ? node.input : node) : (node.input || node);
166 | oconnect.apply(this, arguments);
167 | return node;
168 | }
169 | }
170 |
171 | function dbToWAVolume(db) {
172 | return Math.max(0, Math.round(100 * Math.pow(2, db / 6)) / 100);
173 | }
174 |
175 | function fmod(x, y) {
176 | // http://kevin.vanzonneveld.net
177 | // * example 1: fmod(5.7, 1.3);
178 | // * returns 1: 0.5
179 | var tmp, tmp2, p = 0,
180 | pY = 0,
181 | l = 0.0,
182 | l2 = 0.0;
183 |
184 | tmp = x.toExponential().match(/^.\.?(.*)e(.+)$/);
185 | p = parseInt(tmp[2], 10) - (tmp[1] + "").length;
186 | tmp = y.toExponential().match(/^.\.?(.*)e(.+)$/);
187 | pY = parseInt(tmp[2], 10) - (tmp[1] + "").length;
188 |
189 | if (pY > p) {
190 | p = pY;
191 | }
192 |
193 | tmp2 = (x % y);
194 |
195 | if (p < -100 || p > 20) {
196 | // toFixed will give an out of bound error so we fix it like this:
197 | l = Math.round(Math.log(tmp2) / Math.log(10));
198 | l2 = Math.pow(10, l);
199 |
200 | return (tmp2 / l2).toFixed(l - p) * l2;
201 | } else {
202 | return parseFloat(tmp2.toFixed(-p));
203 | }
204 | }
205 |
206 | function sign(x) {
207 | if (x === 0) {
208 | return 1;
209 | } else {
210 | return Math.abs(x) / x;
211 | }
212 | }
213 |
214 | function tanh(n) {
215 | return (Math.exp(n) - Math.exp(-n)) / (Math.exp(n) + Math.exp(-n));
216 | }
217 |
218 | function initValue(userVal, defaultVal) {
219 | return userVal === undefined ? defaultVal : userVal;
220 | }
221 |
222 | Tuna.prototype.Bitcrusher = function(properties) {
223 | if (!properties) {
224 | properties = this.getDefaults();
225 | }
226 | this.bufferSize = properties.bufferSize || this.defaults.bufferSize.value;
227 |
228 | this.input = userContext.createGain();
229 | this.activateNode = userContext.createGain();
230 | this.processor = userContext.createScriptProcessor(this.bufferSize, 1, 1);
231 | this.output = userContext.createGain();
232 |
233 | this.activateNode.connect(this.processor);
234 | this.processor.connect(this.output);
235 |
236 | var phaser = 0,
237 | last = 0,
238 | input, output, step, i, length;
239 | this.processor.onaudioprocess = function(e) {
240 | input = e.inputBuffer.getChannelData(0),
241 | output = e.outputBuffer.getChannelData(0),
242 | step = Math.pow(1 / 2, this.bits);
243 | length = input.length;
244 | for (i = 0; i < length; i++) {
245 | phaser += this.normfreq;
246 | if (phaser >= 1.0) {
247 | phaser -= 1.0;
248 | last = step * Math.floor(input[i] / step + 0.5);
249 | }
250 | output[i] = last;
251 | }
252 | };
253 |
254 | this.bits = properties.bits || this.defaults.bits.value;
255 | this.normfreq = initValue(properties.normfreq, this.defaults.normfreq.value);
256 | this.bypass = properties.bypass || this.defaults.bypass.value;
257 | };
258 | Tuna.prototype.Bitcrusher.prototype = Object.create(Super, {
259 | name: {
260 | value: "Bitcrusher"
261 | },
262 | defaults: {
263 | writable: true,
264 | value: {
265 | bits: {
266 | value: 4,
267 | min: 1,
268 | max: 16,
269 | automatable: false,
270 | type: INT
271 | },
272 | bufferSize: {
273 | value: 4096,
274 | min: 256,
275 | max: 16384,
276 | automatable: false,
277 | type: INT
278 | },
279 | bypass: {
280 | value: false,
281 | automatable: false,
282 | type: BOOLEAN
283 | },
284 | normfreq: {
285 | value: 0.1,
286 | min: 0.0001,
287 | max: 1.0,
288 | automatable: false,
289 | type: FLOAT
290 | }
291 | }
292 | },
293 | bits: {
294 | enumerable: true,
295 | get: function() {
296 | return this.processor.bits;
297 | },
298 | set: function(value) {
299 | this.processor.bits = value;
300 | }
301 | },
302 | normfreq: {
303 | enumerable: true,
304 | get: function() {
305 | return this.processor.normfreq;
306 | },
307 | set: function(value) {
308 | this.processor.normfreq = value;
309 | }
310 | }
311 | });
312 |
313 | Tuna.prototype.Cabinet = function(properties) {
314 | if (!properties) {
315 | properties = this.getDefaults();
316 | }
317 | this.input = userContext.createGain();
318 | this.activateNode = userContext.createGain();
319 | this.convolver = this.newConvolver(properties.impulsePath || "../impulses/impulse_guitar.wav");
320 | this.makeupNode = userContext.createGain();
321 | this.output = userContext.createGain();
322 |
323 | this.activateNode.connect(this.convolver.input);
324 | this.convolver.output.connect(this.makeupNode);
325 | this.makeupNode.connect(this.output);
326 | //don't use makeupGain setter at init to avoid smoothing
327 | this.makeupNode.gain.value = initValue(properties.makeupGain, this.defaults.makeupGain.value);
328 | this.bypass = properties.bypass || this.defaults.bypass.value;
329 | };
330 | Tuna.prototype.Cabinet.prototype = Object.create(Super, {
331 | name: {
332 | value: "Cabinet"
333 | },
334 | defaults: {
335 | writable: true,
336 | value: {
337 | makeupGain: {
338 | value: 1,
339 | min: 0,
340 | max: 20,
341 | automatable: true,
342 | type: FLOAT
343 | },
344 | bypass: {
345 | value: false,
346 | automatable: false,
347 | type: BOOLEAN
348 | }
349 | }
350 | },
351 | makeupGain: {
352 | enumerable: true,
353 | get: function() {
354 | return this.makeupNode.gain;
355 | },
356 | set: function(value) {
357 | this.makeupNode.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
358 | }
359 | },
360 | newConvolver: {
361 | value: function(impulsePath) {
362 | return new userInstance.Convolver({
363 | impulse: impulsePath,
364 | dryLevel: 0,
365 | wetLevel: 1
366 | });
367 | }
368 | }
369 | });
370 |
371 | Tuna.prototype.Chorus = function(properties) {
372 | if (!properties) {
373 | properties = this.getDefaults();
374 | }
375 | this.input = userContext.createGain();
376 | this.attenuator = this.activateNode = userContext.createGain();
377 | this.splitter = userContext.createChannelSplitter(2);
378 | this.delayL = userContext.createDelay();
379 | this.delayR = userContext.createDelay();
380 | this.feedbackGainNodeLR = userContext.createGain();
381 | this.feedbackGainNodeRL = userContext.createGain();
382 | this.merger = userContext.createChannelMerger(2);
383 | this.output = userContext.createGain();
384 |
385 | this.lfoL = new userInstance.LFO({
386 | target: this.delayL.delayTime,
387 | callback: pipe
388 | });
389 | this.lfoR = new userInstance.LFO({
390 | target: this.delayR.delayTime,
391 | callback: pipe
392 | });
393 |
394 | this.input.connect(this.attenuator);
395 | this.attenuator.connect(this.output);
396 | this.attenuator.connect(this.splitter);
397 | this.splitter.connect(this.delayL, 0);
398 | this.splitter.connect(this.delayR, 1);
399 | this.delayL.connect(this.feedbackGainNodeLR);
400 | this.delayR.connect(this.feedbackGainNodeRL);
401 | this.feedbackGainNodeLR.connect(this.delayR);
402 | this.feedbackGainNodeRL.connect(this.delayL);
403 | this.delayL.connect(this.merger, 0, 0);
404 | this.delayR.connect(this.merger, 0, 1);
405 | this.merger.connect(this.output);
406 |
407 | this.feedback = initValue(properties.feedback, this.defaults.feedback.value);
408 | this.rate = initValue(properties.rate, this.defaults.rate.value);
409 | this.delay = initValue(properties.delay, this.defaults.delay.value);
410 | this.depth = initValue(properties.depth, this.defaults.depth.value);
411 | this.lfoR.phase = Math.PI / 2;
412 | this.attenuator.gain.value = 0.6934; // 1 / (10 ^ (((20 * log10(3)) / 3) / 20))
413 | this.lfoL.activate(true);
414 | this.lfoR.activate(true);
415 | this.bypass = properties.bypass || this.defaults.bypass.value;
416 | };
417 | Tuna.prototype.Chorus.prototype = Object.create(Super, {
418 | name: {
419 | value: "Chorus"
420 | },
421 | defaults: {
422 | writable: true,
423 | value: {
424 | feedback: {
425 | value: 0.4,
426 | min: 0,
427 | max: 0.95,
428 | automatable: false,
429 | type: FLOAT
430 | },
431 | delay: {
432 | value: 0.0045,
433 | min: 0,
434 | max: 1,
435 | automatable: false,
436 | type: FLOAT
437 | },
438 | depth: {
439 | value: 0.7,
440 | min: 0,
441 | max: 1,
442 | automatable: false,
443 | type: FLOAT
444 | },
445 | rate: {
446 | value: 1.5,
447 | min: 0,
448 | max: 8,
449 | automatable: false,
450 | type: FLOAT
451 | },
452 | bypass: {
453 | value: false,
454 | automatable: false,
455 | type: BOOLEAN
456 | }
457 | }
458 | },
459 | delay: {
460 | enumerable: true,
461 | get: function() {
462 | return this._delay;
463 | },
464 | set: function(value) {
465 | this._delay = 0.0002 * (Math.pow(10, value) * 2);
466 | this.lfoL.offset = this._delay;
467 | this.lfoR.offset = this._delay;
468 | this._depth = this._depth;
469 | }
470 | },
471 | depth: {
472 | enumerable: true,
473 | get: function() {
474 | return this._depth;
475 | },
476 | set: function(value) {
477 | this._depth = value;
478 | this.lfoL.oscillation = this._depth * this._delay;
479 | this.lfoR.oscillation = this._depth * this._delay;
480 | }
481 | },
482 | feedback: {
483 | enumerable: true,
484 | get: function() {
485 | return this._feedback;
486 | },
487 | set: function(value) {
488 | this._feedback = value;
489 | this.feedbackGainNodeLR.gain.setTargetAtTime(this._feedback, userContext.currentTime, 0.01);
490 | this.feedbackGainNodeRL.gain.setTargetAtTime(this._feedback, userContext.currentTime, 0.01);
491 | }
492 | },
493 | rate: {
494 | enumerable: true,
495 | get: function() {
496 | return this._rate;
497 | },
498 | set: function(value) {
499 | this._rate = value;
500 | this.lfoL.frequency = this._rate;
501 | this.lfoR.frequency = this._rate;
502 | }
503 | }
504 | });
505 |
506 | Tuna.prototype.Compressor = function(properties) {
507 | if (!properties) {
508 | properties = this.getDefaults();
509 | }
510 | this.input = userContext.createGain();
511 | this.compNode = this.activateNode = userContext.createDynamicsCompressor();
512 | this.makeupNode = userContext.createGain();
513 | this.output = userContext.createGain();
514 |
515 | this.compNode.connect(this.makeupNode);
516 | this.makeupNode.connect(this.output);
517 |
518 | this.automakeup = initValue(properties.automakeup, this.defaults.automakeup.value);
519 |
520 | //don't use makeupGain setter at initialization to avoid smoothing
521 | if (this.automakeup) {
522 | this.makeupNode.gain.value = dbToWAVolume(this.computeMakeup());
523 | } else {
524 | this.makeupNode.gain.value = dbToWAVolume(initValue(properties.makeupGain, this.defaults.makeupGain.value));
525 | }
526 | this.threshold = initValue(properties.threshold, this.defaults.threshold.value);
527 | this.release = initValue(properties.release, this.defaults.release.value);
528 | this.attack = initValue(properties.attack, this.defaults.attack.value);
529 | this.ratio = properties.ratio || this.defaults.ratio.value;
530 | this.knee = initValue(properties.knee, this.defaults.knee.value);
531 | this.bypass = properties.bypass || this.defaults.bypass.value;
532 | };
533 | Tuna.prototype.Compressor.prototype = Object.create(Super, {
534 | name: {
535 | value: "Compressor"
536 | },
537 | defaults: {
538 | writable: true,
539 | value: {
540 | threshold: {
541 | value: -20,
542 | min: -60,
543 | max: 0,
544 | automatable: true,
545 | type: FLOAT
546 | },
547 | release: {
548 | value: 250,
549 | min: 10,
550 | max: 2000,
551 | automatable: true,
552 | type: FLOAT
553 | },
554 | makeupGain: {
555 | value: 1,
556 | min: 1,
557 | max: 100,
558 | automatable: true,
559 | type: FLOAT
560 | },
561 | attack: {
562 | value: 1,
563 | min: 0,
564 | max: 1000,
565 | automatable: true,
566 | type: FLOAT
567 | },
568 | ratio: {
569 | value: 4,
570 | min: 1,
571 | max: 50,
572 | automatable: true,
573 | type: FLOAT
574 | },
575 | knee: {
576 | value: 5,
577 | min: 0,
578 | max: 40,
579 | automatable: true,
580 | type: FLOAT
581 | },
582 | automakeup: {
583 | value: false,
584 | automatable: false,
585 | type: BOOLEAN
586 | },
587 | bypass: {
588 | value: false,
589 | automatable: false,
590 | type: BOOLEAN
591 | }
592 | }
593 | },
594 | computeMakeup: {
595 | value: function() {
596 | var magicCoefficient = 4, // raise me if the output is too hot
597 | c = this.compNode;
598 | return -(c.threshold.value - c.threshold.value / c.ratio.value) / magicCoefficient;
599 | }
600 | },
601 | automakeup: {
602 | enumerable: true,
603 | get: function() {
604 | return this._automakeup;
605 | },
606 | set: function(value) {
607 | this._automakeup = value;
608 | if (this._automakeup) this.makeupGain = this.computeMakeup();
609 | }
610 | },
611 | threshold: {
612 | enumerable: true,
613 | get: function() {
614 | return this.compNode.threshold;
615 | },
616 | set: function(value) {
617 | this.compNode.threshold.value = value;
618 | if (this._automakeup) this.makeupGain = this.computeMakeup();
619 | }
620 | },
621 | ratio: {
622 | enumerable: true,
623 | get: function() {
624 | return this.compNode.ratio;
625 | },
626 | set: function(value) {
627 | this.compNode.ratio.value = value;
628 | if (this._automakeup) this.makeupGain = this.computeMakeup();
629 | }
630 | },
631 | knee: {
632 | enumerable: true,
633 | get: function() {
634 | return this.compNode.knee;
635 | },
636 | set: function(value) {
637 | this.compNode.knee.value = value;
638 | if (this._automakeup) this.makeupGain = this.computeMakeup();
639 | }
640 | },
641 | attack: {
642 | enumerable: true,
643 | get: function() {
644 | return this.compNode.attack;
645 | },
646 | set: function(value) {
647 | this.compNode.attack.value = value / 1000;
648 | }
649 | },
650 | release: {
651 | enumerable: true,
652 | get: function() {
653 | return this.compNode.release;
654 | },
655 | set: function(value) {
656 | this.compNode.release.value = value / 1000;
657 | }
658 | },
659 | makeupGain: {
660 | enumerable: true,
661 | get: function() {
662 | return this.makeupNode.gain;
663 | },
664 | set: function(value) {
665 | this.makeupNode.gain.setTargetAtTime(dbToWAVolume(value), userContext.currentTime, 0.01);
666 | }
667 | }
668 | });
669 |
670 | Tuna.prototype.Convolver = function(properties) {
671 | if (!properties) {
672 | properties = this.getDefaults();
673 | }
674 | this.input = userContext.createGain();
675 | this.activateNode = userContext.createGain();
676 | this.convolver = userContext.createConvolver();
677 | this.dry = userContext.createGain();
678 | this.filterLow = userContext.createBiquadFilter();
679 | this.filterHigh = userContext.createBiquadFilter();
680 | this.wet = userContext.createGain();
681 | this.output = userContext.createGain();
682 |
683 | this.activateNode.connect(this.filterLow);
684 | this.activateNode.connect(this.dry);
685 | this.filterLow.connect(this.filterHigh);
686 | this.filterHigh.connect(this.convolver);
687 | this.convolver.connect(this.wet);
688 | this.wet.connect(this.output);
689 | this.dry.connect(this.output);
690 |
691 | //don't use setters at init to avoid smoothing
692 | this.dry.gain.value = initValue(properties.dryLevel, this.defaults.dryLevel.value);
693 | this.wet.gain.value = initValue(properties.wetLevel, this.defaults.wetLevel.value);
694 | this.filterHigh.frequency.value = properties.highCut || this.defaults.highCut.value;
695 | this.filterLow.frequency.value = properties.lowCut || this.defaults.lowCut.value;
696 | this.output.gain.value = initValue(properties.level, this.defaults.level.value);
697 | this.filterHigh.type = "lowpass";
698 | this.filterLow.type = "highpass";
699 | this.buffer = properties.impulse || "../impulses/ir_rev_short.wav";
700 | this.bypass = properties.bypass || this.defaults.bypass.value;
701 | };
702 | Tuna.prototype.Convolver.prototype = Object.create(Super, {
703 | name: {
704 | value: "Convolver"
705 | },
706 | defaults: {
707 | writable: true,
708 | value: {
709 | highCut: {
710 | value: 22050,
711 | min: 20,
712 | max: 22050,
713 | automatable: true,
714 | type: FLOAT
715 | },
716 | lowCut: {
717 | value: 20,
718 | min: 20,
719 | max: 22050,
720 | automatable: true,
721 | type: FLOAT
722 | },
723 | dryLevel: {
724 | value: 1,
725 | min: 0,
726 | max: 1,
727 | automatable: true,
728 | type: FLOAT
729 | },
730 | wetLevel: {
731 | value: 1,
732 | min: 0,
733 | max: 1,
734 | automatable: true,
735 | type: FLOAT
736 | },
737 | level: {
738 | value: 1,
739 | min: 0,
740 | max: 1,
741 | automatable: true,
742 | type: FLOAT
743 | },
744 | bypass: {
745 | value: false,
746 | automatable: false,
747 | type: BOOLEAN
748 | }
749 | }
750 | },
751 | lowCut: {
752 | get: function() {
753 | return this.filterLow.frequency;
754 | },
755 | set: function(value) {
756 | this.filterLow.frequency.setTargetAtTime(value, userContext.currentTime, 0.01);
757 | }
758 | },
759 | highCut: {
760 | get: function() {
761 | return this.filterHigh.frequency;
762 | },
763 | set: function(value) {
764 | this.filterHigh.frequency.setTargetAtTime(value, userContext.currentTime, 0.01);
765 | }
766 | },
767 | level: {
768 | get: function() {
769 | return this.output.gain;
770 | },
771 | set: function(value) {
772 | this.output.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
773 | }
774 | },
775 | dryLevel: {
776 | get: function() {
777 | return this.dry.gain;
778 | },
779 | set: function(value) {
780 | this.dry.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
781 | }
782 | },
783 | wetLevel: {
784 | get: function() {
785 | return this.wet.gain;
786 | },
787 | set: function(value) {
788 | this.wet.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
789 | }
790 | },
791 | buffer: {
792 | enumerable: false,
793 | get: function() {
794 | return this.convolver.buffer;
795 | },
796 | set: function(impulse) {
797 | var convolver = this.convolver,
798 | xhr = new XMLHttpRequest();
799 | if (!impulse) {
800 | console.log("Tuna.Convolver.setBuffer: Missing impulse path!");
801 | return;
802 | }
803 | xhr.open("GET", impulse, true);
804 | xhr.responseType = "arraybuffer";
805 | xhr.onreadystatechange = function() {
806 | if (xhr.readyState === 4) {
807 | if (xhr.status < 300 && xhr.status > 199 || xhr.status === 302) {
808 | userContext.decodeAudioData(xhr.response, function(buffer) {
809 | convolver.buffer = buffer;
810 | }, function(e) {
811 | if (e) console.log("Tuna.Convolver.setBuffer: Error decoding data" + e);
812 | });
813 | }
814 | }
815 | };
816 | xhr.send(null);
817 | }
818 | }
819 | });
820 |
821 | Tuna.prototype.Delay = function(properties) {
822 | if (!properties) {
823 | properties = this.getDefaults();
824 | }
825 | this.input = userContext.createGain();
826 | this.activateNode = userContext.createGain();
827 | this.dry = userContext.createGain();
828 | this.wet = userContext.createGain();
829 | this.filter = userContext.createBiquadFilter();
830 | this.delay = userContext.createDelay(10);
831 | this.feedbackNode = userContext.createGain();
832 | this.output = userContext.createGain();
833 |
834 | this.activateNode.connect(this.delay);
835 | this.activateNode.connect(this.dry);
836 | this.delay.connect(this.filter);
837 | this.filter.connect(this.feedbackNode);
838 | this.feedbackNode.connect(this.delay);
839 | this.feedbackNode.connect(this.wet);
840 | this.wet.connect(this.output);
841 | this.dry.connect(this.output);
842 |
843 | this.delayTime = properties.delayTime || this.defaults.delayTime.value;
844 | //don't use setters at init to avoid smoothing
845 | this.feedbackNode.gain.value = initValue(properties.feedback, this.defaults.feedback.value);
846 | this.wet.gain.value = initValue(properties.wetLevel, this.defaults.wetLevel.value);
847 | this.dry.gain.value = initValue(properties.dryLevel, this.defaults.dryLevel.value);
848 | this.filter.frequency.value = properties.cutoff || this.defaults.cutoff.value;
849 | this.filter.type = "lowpass";
850 | this.bypass = properties.bypass || this.defaults.bypass.value;
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 | type: FLOAT
865 | },
866 | feedback: {
867 | value: 0.45,
868 | min: 0,
869 | max: 0.9,
870 | automatable: true,
871 | type: FLOAT
872 | },
873 | cutoff: {
874 | value: 20000,
875 | min: 20,
876 | max: 20000,
877 | automatable: true,
878 | type: FLOAT
879 | },
880 | wetLevel: {
881 | value: 0.5,
882 | min: 0,
883 | max: 1,
884 | automatable: true,
885 | type: FLOAT
886 | },
887 | dryLevel: {
888 | value: 1,
889 | min: 0,
890 | max: 1,
891 | automatable: true,
892 | type: FLOAT
893 | },
894 | bypass: {
895 | value: false,
896 | automatable: false,
897 | type: BOOLEAN
898 | }
899 | }
900 | },
901 | delayTime: {
902 | enumerable: true,
903 | get: function() {
904 | return this.delay.delayTime;
905 | },
906 | set: function(value) {
907 | this.delay.delayTime.value = value / 1000;
908 | }
909 | },
910 | wetLevel: {
911 | enumerable: true,
912 | get: function() {
913 | return this.wet.gain;
914 | },
915 | set: function(value) {
916 | this.wet.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
917 | }
918 | },
919 | dryLevel: {
920 | enumerable: true,
921 | get: function() {
922 | return this.dry.gain;
923 | },
924 | set: function(value) {
925 | this.dry.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
926 | }
927 | },
928 | feedback: {
929 | enumerable: true,
930 | get: function() {
931 | return this.feedbackNode.gain;
932 | },
933 | set: function(value) {
934 | this.feedbackNode.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
935 | }
936 | },
937 | cutoff: {
938 | enumerable: true,
939 | get: function() {
940 | return this.filter.frequency;
941 | },
942 | set: function(value) {
943 | this.filter.frequency.setTargetAtTime(value, userContext.currentTime, 0.01);
944 | }
945 | }
946 | });
947 |
948 | Tuna.prototype.Filter = function(properties) {
949 | if (!properties) {
950 | properties = this.getDefaults();
951 | }
952 | this.input = userContext.createGain();
953 | this.activateNode = userContext.createGain();
954 | this.filter = userContext.createBiquadFilter();
955 | this.output = userContext.createGain();
956 |
957 | this.activateNode.connect(this.filter);
958 | this.filter.connect(this.output);
959 |
960 | //don't use setters for freq and gain at init to avoid smoothing
961 | this.filter.frequency.value = properties.frequency || this.defaults.frequency.value;
962 | this.Q = properties.resonance || this.defaults.Q.value;
963 | this.filterType = initValue(properties.filterType, this.defaults.filterType.value);
964 | this.filter.gain.value = initValue(properties.gain, this.defaults.gain.value);
965 | this.bypass = properties.bypass || this.defaults.bypass.value;
966 | };
967 | Tuna.prototype.Filter.prototype = Object.create(Super, {
968 | name: {
969 | value: "Filter"
970 | },
971 | defaults: {
972 | writable: true,
973 | value: {
974 | frequency: {
975 | value: 800,
976 | min: 20,
977 | max: 22050,
978 | automatable: true,
979 | type: FLOAT
980 | },
981 | Q: {
982 | value: 1,
983 | min: 0.001,
984 | max: 100,
985 | automatable: true,
986 | type: FLOAT
987 | },
988 | gain: {
989 | value: 0,
990 | min: -40,
991 | max: 40,
992 | automatable: true,
993 | type: FLOAT
994 | },
995 | bypass: {
996 | value: false,
997 | automatable: false,
998 | type: BOOLEAN
999 | },
1000 | filterType: {
1001 | value: "lowpass",
1002 | automatable: false,
1003 | type: STRING
1004 | }
1005 | }
1006 | },
1007 | filterType: {
1008 | enumerable: true,
1009 | get: function() {
1010 | return this.filter.type;
1011 | },
1012 | set: function(value) {
1013 | this.filter.type = value;
1014 | }
1015 | },
1016 | Q: {
1017 | enumerable: true,
1018 | get: function() {
1019 | return this.filter.Q;
1020 | },
1021 | set: function(value) {
1022 | this.filter.Q.value = value;
1023 | }
1024 | },
1025 | gain: {
1026 | enumerable: true,
1027 | get: function() {
1028 | return this.filter.gain;
1029 | },
1030 | set: function(value) {
1031 | this.filter.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
1032 | }
1033 | },
1034 | frequency: {
1035 | enumerable: true,
1036 | get: function() {
1037 | return this.filter.frequency;
1038 | },
1039 | set: function(value) {
1040 | this.filter.frequency.setTargetAtTime(value, userContext.currentTime, 0.01);
1041 | }
1042 | }
1043 | });
1044 |
1045 | Tuna.prototype.Gain = function(properties) {
1046 | if (!properties) {
1047 | properties = this.getDefaults();
1048 | }
1049 |
1050 | this.input = userContext.createGain();
1051 | this.activateNode = userContext.createGain();
1052 | this.gainNode = userContext.createGain();
1053 | this.output = userContext.createGain();
1054 |
1055 | this.activateNode.connect(this.gainNode);
1056 | this.gainNode.connect(this.output);
1057 |
1058 | //don't use setter at init to avoid smoothing
1059 | this.gainNode.gain.value = initValue(properties.gain, this.defaults.gain.value);
1060 | this.bypass = properties.bypass || this.defaults.bypass.value;
1061 | };
1062 | Tuna.prototype.Gain.prototype = Object.create(Super, {
1063 | name: {
1064 | value: "Gain"
1065 | },
1066 | defaults: {
1067 | writable: true,
1068 | value: {
1069 | bypass: {
1070 | value: false,
1071 | automatable: false,
1072 | type: BOOLEAN
1073 | },
1074 | gain: {
1075 | value: 1.0,
1076 | automatable: true,
1077 | type: FLOAT
1078 | }
1079 | }
1080 | },
1081 | gain: {
1082 | enumerable: true,
1083 | get: function() {
1084 | return this.gainNode.gain;
1085 | },
1086 | set: function(value) {
1087 | this.gainNode.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
1088 | }
1089 | }
1090 | });
1091 |
1092 | Tuna.prototype.MoogFilter = function(properties) {
1093 | if (!properties) {
1094 | properties = this.getDefaults();
1095 | }
1096 | this.bufferSize = properties.bufferSize || this.defaults.bufferSize.value;
1097 |
1098 | this.input = userContext.createGain();
1099 | this.activateNode = userContext.createGain();
1100 | this.processor = userContext.createScriptProcessor(this.bufferSize, 1, 1);
1101 | this.output = userContext.createGain();
1102 |
1103 | this.activateNode.connect(this.processor);
1104 | this.processor.connect(this.output);
1105 |
1106 | var in1, in2, in3, in4, out1, out2, out3, out4;
1107 | in1 = in2 = in3 = in4 = out1 = out2 = out3 = out4 = 0.0;
1108 | var input, output, f, fb, i, length, inputFactor;
1109 | this.processor.onaudioprocess = function(e) {
1110 | input = e.inputBuffer.getChannelData(0);
1111 | output = e.outputBuffer.getChannelData(0);
1112 | f = this.cutoff * 1.16;
1113 | inputFactor = 0.35013 * (f * f) * (f * f);
1114 | fb = this.resonance * (1.0 - 0.15 * f * f);
1115 | length = input.length;
1116 | for (i = 0; i < length; i++) {
1117 | input[i] -= out4 * fb;
1118 | input[i] *= inputFactor;
1119 | out1 = input[i] + 0.3 * in1 + (1 - f) * out1; // Pole 1
1120 | in1 = input[i];
1121 | out2 = out1 + 0.3 * in2 + (1 - f) * out2; // Pole 2
1122 | in2 = out1;
1123 | out3 = out2 + 0.3 * in3 + (1 - f) * out3; // Pole 3
1124 | in3 = out2;
1125 | out4 = out3 + 0.3 * in4 + (1 - f) * out4; // Pole 4
1126 | in4 = out3;
1127 | output[i] = out4;
1128 | }
1129 | };
1130 |
1131 | this.cutoff = initValue(properties.cutoff, this.defaults.cutoff.value);
1132 | this.resonance = initValue(properties.resonance, this.defaults.resonance.value);
1133 | this.bypass = properties.bypass || this.defaults.bypass.value;
1134 | };
1135 | Tuna.prototype.MoogFilter.prototype = Object.create(Super, {
1136 | name: {
1137 | value: "MoogFilter"
1138 | },
1139 | defaults: {
1140 | writable: true,
1141 | value: {
1142 | bufferSize: {
1143 | value: 4096,
1144 | min: 256,
1145 | max: 16384,
1146 | automatable: false,
1147 | type: INT
1148 | },
1149 | bypass: {
1150 | value: false,
1151 | automatable: false,
1152 | type: BOOLEAN
1153 | },
1154 | cutoff: {
1155 | value: 0.065,
1156 | min: 0.0001,
1157 | max: 1.0,
1158 | automatable: false,
1159 | type: FLOAT
1160 | },
1161 | resonance: {
1162 | value: 3.5,
1163 | min: 0.0,
1164 | max: 4.0,
1165 | automatable: false,
1166 | type: FLOAT
1167 | }
1168 | }
1169 | },
1170 | cutoff: {
1171 | enumerable: true,
1172 | get: function() {
1173 | return this.processor.cutoff;
1174 | },
1175 | set: function(value) {
1176 | this.processor.cutoff = value;
1177 | }
1178 | },
1179 | resonance: {
1180 | enumerable: true,
1181 | get: function() {
1182 | return this.processor.resonance;
1183 | },
1184 | set: function(value) {
1185 | this.processor.resonance = value;
1186 | }
1187 | }
1188 | });
1189 |
1190 | Tuna.prototype.Overdrive = function(properties) {
1191 | if (!properties) {
1192 | properties = this.getDefaults();
1193 | }
1194 | this.input = userContext.createGain();
1195 | this.activateNode = userContext.createGain();
1196 | this.inputDrive = userContext.createGain();
1197 | this.waveshaper = userContext.createWaveShaper();
1198 | this.outputDrive = userContext.createGain();
1199 | this.output = userContext.createGain();
1200 |
1201 | this.activateNode.connect(this.inputDrive);
1202 | this.inputDrive.connect(this.waveshaper);
1203 | this.waveshaper.connect(this.outputDrive);
1204 | this.outputDrive.connect(this.output);
1205 |
1206 | this.ws_table = new Float32Array(this.k_nSamples);
1207 | this.drive = initValue(properties.drive, this.defaults.drive.value);
1208 | this.outputGain = initValue(properties.outputGain, this.defaults.outputGain.value);
1209 | this.curveAmount = initValue(properties.curveAmount, this.defaults.curveAmount.value);
1210 | this.algorithmIndex = initValue(properties.algorithmIndex, this.defaults.algorithmIndex.value);
1211 | this.bypass = properties.bypass || this.defaults.bypass.value;
1212 | };
1213 | Tuna.prototype.Overdrive.prototype = Object.create(Super, {
1214 | name: {
1215 | value: "Overdrive"
1216 | },
1217 | defaults: {
1218 | writable: true,
1219 | value: {
1220 | drive: {
1221 | value: 0.197,
1222 | min: 0,
1223 | max: 1,
1224 | automatable: true,
1225 | type: FLOAT,
1226 | scaled: true
1227 | },
1228 | outputGain: {
1229 | value: -9.154,
1230 | min: -46,
1231 | max: 0,
1232 | automatable: true,
1233 | type: FLOAT,
1234 | scaled: true
1235 | },
1236 | curveAmount: {
1237 | value: 0.979,
1238 | min: 0,
1239 | max: 1,
1240 | automatable: false,
1241 | type: FLOAT
1242 | },
1243 | algorithmIndex: {
1244 | value: 0,
1245 | min: 0,
1246 | max: 5,
1247 | automatable: false,
1248 | type: INT
1249 | },
1250 | bypass: {
1251 | value: false,
1252 | automatable: false,
1253 | type: BOOLEAN
1254 | }
1255 | }
1256 | },
1257 | k_nSamples: {
1258 | value: 8192
1259 | },
1260 | drive: {
1261 | get: function() {
1262 | return this.inputDrive.gain;
1263 | },
1264 | set: function(value) {
1265 | this.inputDrive.gain.value = value;
1266 | }
1267 | },
1268 | curveAmount: {
1269 | get: function() {
1270 | return this._curveAmount;
1271 | },
1272 | set: function(value) {
1273 | this._curveAmount = value;
1274 | if (this._algorithmIndex === undefined) {
1275 | this._algorithmIndex = 0;
1276 | }
1277 | this.waveshaperAlgorithms[this._algorithmIndex](this._curveAmount, this.k_nSamples, this.ws_table);
1278 | this.waveshaper.curve = this.ws_table;
1279 | }
1280 | },
1281 | outputGain: {
1282 | get: function() {
1283 | return this.outputDrive.gain;
1284 | },
1285 | set: function(value) {
1286 | this._outputGain = dbToWAVolume(value);
1287 | this.outputDrive.gain.setValueAtTime(this._outputGain, userContext.currentTime, 0.01);
1288 | }
1289 | },
1290 | algorithmIndex: {
1291 | get: function() {
1292 | return this._algorithmIndex;
1293 | },
1294 | set: function(value) {
1295 | this._algorithmIndex = value;
1296 | this.curveAmount = this._curveAmount;
1297 | }
1298 | },
1299 | waveshaperAlgorithms: {
1300 | value: [
1301 | function(amount, n_samples, ws_table) {
1302 | amount = Math.min(amount, 0.9999);
1303 | var k = 2 * amount / (1 - amount),
1304 | i, x;
1305 | for (i = 0; i < n_samples; i++) {
1306 | x = i * 2 / n_samples - 1;
1307 | ws_table[i] = (1 + k) * x / (1 + k * Math.abs(x));
1308 | }
1309 | },
1310 | function(amount, n_samples, ws_table) {
1311 | var i, x, y;
1312 | for (i = 0; i < n_samples; i++) {
1313 | x = i * 2 / n_samples - 1;
1314 | y = ((0.5 * Math.pow((x + 1.4), 2)) - 1) * (y >= 0 ? 5.8 : 1.2);
1315 | ws_table[i] = tanh(y);
1316 | }
1317 | },
1318 | function(amount, n_samples, ws_table) {
1319 | var i, x, y, a = 1 - amount;
1320 | for (i = 0; i < n_samples; i++) {
1321 | x = i * 2 / n_samples - 1;
1322 | y = x < 0 ? -Math.pow(Math.abs(x), a + 0.04) : Math.pow(x, a);
1323 | ws_table[i] = tanh(y * 2);
1324 | }
1325 | },
1326 | function(amount, n_samples, ws_table) {
1327 | var i, x, y, abx, a = 1 - amount > 0.99 ? 0.99 : 1 - amount;
1328 | for (i = 0; i < n_samples; i++) {
1329 | x = i * 2 / n_samples - 1;
1330 | abx = Math.abs(x);
1331 | if (abx < a) {
1332 | y = abx;
1333 | } else if (abx > a) {
1334 | y = a + (abx - a) / (1 + Math.pow((abx - a) / (1 - a), 2));
1335 | } else if (abx > 1) {
1336 | y = abx;
1337 | }
1338 | ws_table[i] = sign(x) * y * (1 / ((a + 1) / 2));
1339 | }
1340 | },
1341 | function(amount, n_samples, ws_table) { // fixed curve, amount doesn't do anything, the distortion is just from the drive
1342 | var i, x;
1343 | for (i = 0; i < n_samples; i++) {
1344 | x = i * 2 / n_samples - 1;
1345 | if (x < -0.08905) {
1346 | ws_table[i] = (-3 / 4) * (1 - (Math.pow((1 - (Math.abs(x) - 0.032857)), 12)) + (1 / 3) * (Math.abs(x) - 0.032847)) + 0.01;
1347 | } else if (x >= -0.08905 && x < 0.320018) {
1348 | ws_table[i] = (-6.153 * (x * x)) + 3.9375 * x;
1349 | } else {
1350 | ws_table[i] = 0.630035;
1351 | }
1352 | }
1353 | },
1354 | function(amount, n_samples, ws_table) {
1355 | var a = 2 + Math.round(amount * 14),
1356 | // we go from 2 to 16 bits, keep in mind for the UI
1357 | bits = Math.round(Math.pow(2, a - 1)),
1358 | // real number of quantization steps divided by 2
1359 | i, x;
1360 | for (i = 0; i < n_samples; i++) {
1361 | x = i * 2 / n_samples - 1;
1362 | ws_table[i] = Math.round(x * bits) / bits;
1363 | }
1364 | }
1365 | ]
1366 | }
1367 | });
1368 |
1369 | Tuna.prototype.Panner = function(properties) {
1370 | if (!properties) {
1371 | properties = this.getDefaults();
1372 | }
1373 |
1374 | this.input = userContext.createGain();
1375 | this.activateNode = userContext.createGain();
1376 | this.panner = userContext.createStereoPanner();
1377 | this.output = userContext.createGain();
1378 |
1379 | this.activateNode.connect(this.panner);
1380 | this.panner.connect(this.output);
1381 |
1382 | this.pan = initValue(properties.pan, this.defaults.pan.value);
1383 | this.bypass = properties.bypass || this.defaults.bypass.value;
1384 | };
1385 | Tuna.prototype.Panner.prototype = Object.create(Super, {
1386 | name: {
1387 | value: "Panner"
1388 | },
1389 | defaults: {
1390 | writable: true,
1391 | value: {
1392 | bypass: {
1393 | value: false,
1394 | automatable: false,
1395 | type: BOOLEAN
1396 | },
1397 | pan: {
1398 | value: 0.0,
1399 | min: -1.0,
1400 | max: 1.0,
1401 | automatable: true,
1402 | type: FLOAT
1403 | }
1404 | }
1405 | },
1406 | pan: {
1407 | enumerable: true,
1408 | get: function() {
1409 | return this.panner.pan;
1410 | },
1411 | set: function(value) {
1412 | this.panner.pan.value = value;
1413 | }
1414 | }
1415 | });
1416 |
1417 | Tuna.prototype.Phaser = function(properties) {
1418 | if (!properties) {
1419 | properties = this.getDefaults();
1420 | }
1421 | this.input = userContext.createGain();
1422 | this.splitter = this.activateNode = userContext.createChannelSplitter(2);
1423 | this.filtersL = [];
1424 | this.filtersR = [];
1425 | this.feedbackGainNodeL = userContext.createGain();
1426 | this.feedbackGainNodeR = userContext.createGain();
1427 | this.merger = userContext.createChannelMerger(2);
1428 | this.filteredSignal = userContext.createGain();
1429 | this.output = userContext.createGain();
1430 | this.lfoL = new userInstance.LFO({
1431 | target: this.filtersL,
1432 | callback: this.callback
1433 | });
1434 | this.lfoR = new userInstance.LFO({
1435 | target: this.filtersR,
1436 | callback: this.callback
1437 | });
1438 |
1439 | var i = this.stage;
1440 | while (i--) {
1441 | this.filtersL[i] = userContext.createBiquadFilter();
1442 | this.filtersR[i] = userContext.createBiquadFilter();
1443 | this.filtersL[i].type = "allpass";
1444 | this.filtersR[i].type = "allpass";
1445 | }
1446 | this.input.connect(this.splitter);
1447 | this.input.connect(this.output);
1448 | this.splitter.connect(this.filtersL[0], 0, 0);
1449 | this.splitter.connect(this.filtersR[0], 1, 0);
1450 | this.connectInOrder(this.filtersL);
1451 | this.connectInOrder(this.filtersR);
1452 | this.filtersL[this.stage - 1].connect(this.feedbackGainNodeL);
1453 | this.filtersL[this.stage - 1].connect(this.merger, 0, 0);
1454 | this.filtersR[this.stage - 1].connect(this.feedbackGainNodeR);
1455 | this.filtersR[this.stage - 1].connect(this.merger, 0, 1);
1456 | this.feedbackGainNodeL.connect(this.filtersL[0]);
1457 | this.feedbackGainNodeR.connect(this.filtersR[0]);
1458 | this.merger.connect(this.output);
1459 |
1460 | this.rate = initValue(properties.rate, this.defaults.rate.value);
1461 | this.baseModulationFrequency = properties.baseModulationFrequency || this.defaults.baseModulationFrequency.value;
1462 | this.depth = initValue(properties.depth, this.defaults.depth.value);
1463 | this.feedback = initValue(properties.feedback, this.defaults.feedback.value);
1464 | this.stereoPhase = initValue(properties.stereoPhase, this.defaults.stereoPhase.value);
1465 |
1466 | this.lfoL.activate(true);
1467 | this.lfoR.activate(true);
1468 | this.bypass = properties.bypass || this.defaults.bypass.value;
1469 | };
1470 | Tuna.prototype.Phaser.prototype = Object.create(Super, {
1471 | name: {
1472 | value: "Phaser"
1473 | },
1474 | stage: {
1475 | value: 4
1476 | },
1477 | defaults: {
1478 | writable: true,
1479 | value: {
1480 | rate: {
1481 | value: 0.1,
1482 | min: 0,
1483 | max: 8,
1484 | automatable: false,
1485 | type: FLOAT
1486 | },
1487 | depth: {
1488 | value: 0.6,
1489 | min: 0,
1490 | max: 1,
1491 | automatable: false,
1492 | type: FLOAT
1493 | },
1494 | feedback: {
1495 | value: 0.7,
1496 | min: 0,
1497 | max: 1,
1498 | automatable: false,
1499 | type: FLOAT
1500 | },
1501 | stereoPhase: {
1502 | value: 40,
1503 | min: 0,
1504 | max: 180,
1505 | automatable: false,
1506 | type: FLOAT
1507 | },
1508 | baseModulationFrequency: {
1509 | value: 700,
1510 | min: 500,
1511 | max: 1500,
1512 | automatable: false,
1513 | type: FLOAT
1514 | },
1515 | bypass: {
1516 | value: false,
1517 | automatable: false,
1518 | type: BOOLEAN
1519 | }
1520 | }
1521 | },
1522 | callback: {
1523 | value: function(filters, value) {
1524 | for (var stage = 0; stage < 4; stage++) {
1525 | filters[stage].frequency.value = value;
1526 | }
1527 | }
1528 | },
1529 | depth: {
1530 | get: function() {
1531 | return this._depth;
1532 | },
1533 | set: function(value) {
1534 | this._depth = value;
1535 | this.lfoL.oscillation = this._baseModulationFrequency * this._depth;
1536 | this.lfoR.oscillation = this._baseModulationFrequency * this._depth;
1537 | }
1538 | },
1539 | rate: {
1540 | get: function() {
1541 | return this._rate;
1542 | },
1543 | set: function(value) {
1544 | this._rate = value;
1545 | this.lfoL.frequency = this._rate;
1546 | this.lfoR.frequency = this._rate;
1547 | }
1548 | },
1549 | baseModulationFrequency: {
1550 | enumerable: true,
1551 | get: function() {
1552 | return this._baseModulationFrequency;
1553 | },
1554 | set: function(value) {
1555 | this._baseModulationFrequency = value;
1556 | this.lfoL.offset = this._baseModulationFrequency;
1557 | this.lfoR.offset = this._baseModulationFrequency;
1558 | this.depth = this._depth;
1559 | }
1560 | },
1561 | feedback: {
1562 | get: function() {
1563 | return this._feedback;
1564 | },
1565 | set: function(value) {
1566 | this._feedback = value;
1567 | this.feedbackGainNodeL.gain.setTargetAtTime(this._feedback, userContext.currentTime, 0.01);
1568 | this.feedbackGainNodeR.gain.setTargetAtTime(this._feedback, userContext.currentTime, 0.01);
1569 | }
1570 | },
1571 | stereoPhase: {
1572 | get: function() {
1573 | return this._stereoPhase;
1574 | },
1575 | set: function(value) {
1576 | this._stereoPhase = value;
1577 | var newPhase = this.lfoL._phase + this._stereoPhase * Math.PI / 180;
1578 | newPhase = fmod(newPhase, 2 * Math.PI);
1579 | this.lfoR._phase = newPhase;
1580 | }
1581 | }
1582 | });
1583 |
1584 | Tuna.prototype.PingPongDelay = function(properties) {
1585 | if (!properties) {
1586 | properties = this.getDefaults();
1587 | }
1588 | this.input = userContext.createGain();
1589 | this.wet = userContext.createGain();
1590 | this.stereoToMonoMix = userContext.createGain();
1591 | this.feedbackLevel = userContext.createGain();
1592 | this.output = userContext.createGain();
1593 | this.delayLeft = userContext.createDelay(10);
1594 | this.delayRight = userContext.createDelay(10);
1595 |
1596 | this.activateNode = userContext.createGain();
1597 | this.splitter = userContext.createChannelSplitter(2);
1598 | this.merger = userContext.createChannelMerger(2);
1599 |
1600 | this.activateNode.connect(this.splitter);
1601 | this.splitter.connect(this.stereoToMonoMix, 0, 0);
1602 | this.splitter.connect(this.stereoToMonoMix, 1, 0);
1603 | this.stereoToMonoMix.gain.value = .5;
1604 | this.stereoToMonoMix.connect(this.wet);
1605 | this.wet.connect(this.delayLeft);
1606 | this.feedbackLevel.connect(this.wet);
1607 | this.delayLeft.connect(this.delayRight);
1608 | this.delayRight.connect(this.feedbackLevel);
1609 | this.delayLeft.connect(this.merger, 0, 0);
1610 | this.delayRight.connect(this.merger, 0, 1);
1611 | this.merger.connect(this.output);
1612 | this.activateNode.connect(this.output);
1613 |
1614 | this.delayTimeLeft = properties.delayTimeLeft !== undefined ? properties.delayTimeLeft : this.defaults.delayTimeLeft.value;
1615 | this.delayTimeRight = properties.delayTimeRight !== undefined ? properties.delayTimeRight : this.defaults.delayTimeRight.value;
1616 | this.feedbackLevel.gain.value = properties.feedback !== undefined ? properties.feedback : this.defaults.feedback.value;
1617 | this.wet.gain.value = properties.wetLevel !== undefined ? properties.wetLevel : this.defaults.wetLevel.value;
1618 | this.bypass = properties.bypass || this.defaults.bypass.value;
1619 | };
1620 | Tuna.prototype.PingPongDelay.prototype = Object.create(Super, {
1621 | name: {
1622 | value: "PingPongDelay"
1623 | },
1624 | delayTimeLeft: {
1625 | enumerable: true,
1626 | get: function() {
1627 | return this._delayTimeLeft;
1628 | },
1629 | set: function(value) {
1630 | this._delayTimeLeft = value;
1631 | this.delayLeft.delayTime.value = value / 1000;
1632 | }
1633 | },
1634 | delayTimeRight: {
1635 | enumerable: true,
1636 | get: function() {
1637 | return this._delayTimeRight;
1638 | },
1639 | set: function(value) {
1640 | this._delayTimeRight = value;
1641 | this.delayRight.delayTime.value = value / 1000;
1642 | }
1643 | },
1644 | wetLevel: {
1645 | enumerable: true,
1646 | get: function () {
1647 | return this.wet.gain;
1648 | },
1649 | set: function (value) {
1650 | this.wet.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
1651 | }
1652 | },
1653 | feedback: {
1654 | enumerable: true,
1655 | get: function () {
1656 | return this.feedbackLevel.gain;
1657 | },
1658 | set: function (value) {
1659 | this.feedbackLevel.gain.setTargetAtTime(value, userContext.currentTime, 0.01);
1660 | }
1661 | },
1662 | defaults: {
1663 | writable: true,
1664 | value: {
1665 | delayTimeLeft: {
1666 | value: 200,
1667 | min: 1,
1668 | max: 10000,
1669 | automatable: false,
1670 | type: INT
1671 | },
1672 | delayTimeRight: {
1673 | value: 400,
1674 | min: 1,
1675 | max: 10000,
1676 | automatable: false,
1677 | type: INT
1678 | },
1679 | feedback: {
1680 | value: 0.3,
1681 | min: 0,
1682 | max: 1,
1683 | automatable: true,
1684 | type: FLOAT
1685 | },
1686 | wetLevel: {
1687 | value: 0.5,
1688 | min: 0,
1689 | max: 1,
1690 | automatable: true,
1691 | type: FLOAT
1692 | },
1693 | bypass: {
1694 | value: false,
1695 | automatable: false,
1696 | type: BOOLEAN
1697 | }
1698 | }
1699 | }
1700 | });
1701 |
1702 | Tuna.prototype.Tremolo = function(properties) {
1703 | if (!properties) {
1704 | properties = this.getDefaults();
1705 | }
1706 | this.input = userContext.createGain();
1707 | this.splitter = this.activateNode = userContext.createChannelSplitter(2);
1708 | this.amplitudeL = userContext.createGain();
1709 | this.amplitudeR = userContext.createGain();
1710 | this.merger = userContext.createChannelMerger(2);
1711 | this.output = userContext.createGain();
1712 | this.lfoL = new userInstance.LFO({
1713 | target: this.amplitudeL.gain,
1714 | callback: pipe
1715 | });
1716 | this.lfoR = new userInstance.LFO({
1717 | target: this.amplitudeR.gain,
1718 | callback: pipe
1719 | });
1720 |
1721 | this.input.connect(this.splitter);
1722 | this.splitter.connect(this.amplitudeL, 0);
1723 | this.splitter.connect(this.amplitudeR, 1);
1724 | this.amplitudeL.connect(this.merger, 0, 0);
1725 | this.amplitudeR.connect(this.merger, 0, 1);
1726 | this.merger.connect(this.output);
1727 |
1728 | this.rate = properties.rate || this.defaults.rate.value;
1729 | this.intensity = initValue(properties.intensity, this.defaults.intensity.value);
1730 | this.stereoPhase = initValue(properties.stereoPhase, this.defaults.stereoPhase.value);
1731 |
1732 | this.lfoL.offset = 1 - (this.intensity / 2);
1733 | this.lfoR.offset = 1 - (this.intensity / 2);
1734 | this.lfoL.phase = this.stereoPhase * Math.PI / 180;
1735 |
1736 | this.lfoL.activate(true);
1737 | this.lfoR.activate(true);
1738 | this.bypass = properties.bypass || this.defaults.bypass.value;
1739 | };
1740 | Tuna.prototype.Tremolo.prototype = Object.create(Super, {
1741 | name: {
1742 | value: "Tremolo"
1743 | },
1744 | defaults: {
1745 | writable: true,
1746 | value: {
1747 | intensity: {
1748 | value: 0.3,
1749 | min: 0,
1750 | max: 1,
1751 | automatable: false,
1752 | type: FLOAT
1753 | },
1754 | stereoPhase: {
1755 | value: 0,
1756 | min: 0,
1757 | max: 180,
1758 | automatable: false,
1759 | type: FLOAT
1760 | },
1761 | rate: {
1762 | value: 5,
1763 | min: 0.1,
1764 | max: 11,
1765 | automatable: false,
1766 | type: FLOAT
1767 | },
1768 | bypass: {
1769 | value: false,
1770 | automatable: false,
1771 | type: BOOLEAN
1772 | }
1773 | }
1774 | },
1775 | intensity: {
1776 | enumerable: true,
1777 | get: function() {
1778 | return this._intensity;
1779 | },
1780 | set: function(value) {
1781 | this._intensity = value;
1782 | this.lfoL.offset = 1 - this._intensity / 2;
1783 | this.lfoR.offset = 1 - this._intensity / 2;
1784 | this.lfoL.oscillation = this._intensity;
1785 | this.lfoR.oscillation = this._intensity;
1786 | }
1787 | },
1788 | rate: {
1789 | enumerable: true,
1790 | get: function() {
1791 | return this._rate;
1792 | },
1793 | set: function(value) {
1794 | this._rate = value;
1795 | this.lfoL.frequency = this._rate;
1796 | this.lfoR.frequency = this._rate;
1797 | }
1798 | },
1799 | stereoPhase: {
1800 | enumerable: true,
1801 | get: function() {
1802 | return this._stereoPhase;
1803 | },
1804 | set: function(value) {
1805 | this._stereoPhase = value;
1806 | var newPhase = this.lfoL._phase + this._stereoPhase * Math.PI / 180;
1807 | newPhase = fmod(newPhase, 2 * Math.PI);
1808 | this.lfoR.phase = newPhase;
1809 | }
1810 | }
1811 | });
1812 |
1813 | Tuna.prototype.WahWah = function(properties) {
1814 | if (!properties) {
1815 | properties = this.getDefaults();
1816 | }
1817 | this.input = userContext.createGain();
1818 | this.activateNode = userContext.createGain();
1819 | this.envelopeFollower = new userInstance.EnvelopeFollower({
1820 | target: this,
1821 | callback: function(context, value) {
1822 | context.sweep = value;
1823 | }
1824 | });
1825 | this.filterBp = userContext.createBiquadFilter();
1826 | this.filterPeaking = userContext.createBiquadFilter();
1827 | this.output = userContext.createGain();
1828 |
1829 | //Connect AudioNodes
1830 | this.activateNode.connect(this.filterBp);
1831 | this.filterBp.connect(this.filterPeaking);
1832 | this.filterPeaking.connect(this.output);
1833 |
1834 | //Set Properties
1835 | this.init();
1836 | this.automode = initValue(properties.automode, this.defaults.automode.value);
1837 | this.resonance = properties.resonance || this.defaults.resonance.value;
1838 | this.sensitivity = initValue(properties.sensitivity, this.defaults.sensitivity.value);
1839 | this.baseFrequency = initValue(properties.baseFrequency, this.defaults.baseFrequency.value);
1840 | this.excursionOctaves = properties.excursionOctaves || this.defaults.excursionOctaves.value;
1841 | this.sweep = initValue(properties.sweep, this.defaults.sweep.value);
1842 |
1843 | this.activateNode.gain.value = 2;
1844 | this.envelopeFollower.activate(true);
1845 | this.bypass = properties.bypass || this.defaults.bypass.value;
1846 | };
1847 | Tuna.prototype.WahWah.prototype = Object.create(Super, {
1848 | name: {
1849 | value: "WahWah"
1850 | },
1851 | defaults: {
1852 | writable: true,
1853 | value: {
1854 | automode: {
1855 | value: true,
1856 | automatable: false,
1857 | type: BOOLEAN
1858 | },
1859 | baseFrequency: {
1860 | value: 0.153,
1861 | min: 0,
1862 | max: 1,
1863 | automatable: false,
1864 | type: FLOAT
1865 | },
1866 | excursionOctaves: {
1867 | value: 3.3,
1868 | min: 1,
1869 | max: 6,
1870 | automatable: false,
1871 | type: FLOAT
1872 | },
1873 | sweep: {
1874 | value: 0.35,
1875 | min: 0,
1876 | max: 1,
1877 | automatable: false,
1878 | type: FLOAT
1879 | },
1880 | resonance: {
1881 | value: 19,
1882 | min: 1,
1883 | max: 100,
1884 | automatable: false,
1885 | type: FLOAT
1886 | },
1887 | sensitivity: {
1888 | value: -0.5,
1889 | min: -1,
1890 | max: 1,
1891 | automatable: false,
1892 | type: FLOAT
1893 | },
1894 | bypass: {
1895 | value: false,
1896 | automatable: false,
1897 | type: BOOLEAN
1898 | }
1899 | }
1900 | },
1901 | automode: {
1902 | get: function() {
1903 | return this._automode;
1904 | },
1905 | set: function(value) {
1906 | this._automode = value;
1907 | if (value) {
1908 | this.activateNode.connect(this.envelopeFollower.input);
1909 | this.envelopeFollower.activate(true);
1910 | } else {
1911 | this.envelopeFollower.activate(false);
1912 | this.activateNode.disconnect();
1913 | this.activateNode.connect(this.filterBp);
1914 | }
1915 | }
1916 | },
1917 | filterFreqTimeout: {
1918 | writable: true,
1919 | value: 0
1920 | },
1921 | setFilterFreq: {
1922 | value: function() {
1923 | try {
1924 | this.filterBp.frequency.value = Math.min(22050, this._baseFrequency + this._excursionFrequency * this._sweep);
1925 | this.filterPeaking.frequency.value = Math.min(22050, this._baseFrequency + this._excursionFrequency * this._sweep);
1926 | } catch (e) {
1927 | clearTimeout(this.filterFreqTimeout);
1928 | //put on the next cycle to let all init properties be set
1929 | this.filterFreqTimeout = setTimeout(function() {
1930 | this.setFilterFreq();
1931 | }.bind(this), 0);
1932 | }
1933 | }
1934 | },
1935 | sweep: {
1936 | enumerable: true,
1937 | get: function() {
1938 | return this._sweep;
1939 | },
1940 | set: function(value) {
1941 | this._sweep = Math.pow(value > 1 ? 1 : value < 0 ? 0 : value, this._sensitivity);
1942 | this.setFilterFreq();
1943 | }
1944 | },
1945 | baseFrequency: {
1946 | enumerable: true,
1947 | get: function() {
1948 | return this._baseFrequency;
1949 | },
1950 | set: function(value) {
1951 | this._baseFrequency = 50 * Math.pow(10, value * 2);
1952 | this._excursionFrequency = Math.min(userContext.sampleRate / 2, this.baseFrequency * Math.pow(2, this._excursionOctaves));
1953 | this.setFilterFreq();
1954 | }
1955 | },
1956 | excursionOctaves: {
1957 | enumerable: true,
1958 | get: function() {
1959 | return this._excursionOctaves;
1960 | },
1961 | set: function(value) {
1962 | this._excursionOctaves = value;
1963 | this._excursionFrequency = Math.min(userContext.sampleRate / 2, this.baseFrequency * Math.pow(2, this._excursionOctaves));
1964 | this.setFilterFreq();
1965 | }
1966 | },
1967 | sensitivity: {
1968 | enumerable: true,
1969 | get: function() {
1970 | return this._sensitivity;
1971 | },
1972 | set: function(value) {
1973 | this._sensitivity = Math.pow(10, value);
1974 | }
1975 | },
1976 | resonance: {
1977 | enumerable: true,
1978 | get: function() {
1979 | return this._resonance;
1980 | },
1981 | set: function(value) {
1982 | this._resonance = value;
1983 | this.filterPeaking.Q.value = this._resonance;
1984 | }
1985 | },
1986 | init: {
1987 | value: function() {
1988 | this.output.gain.value = 1;
1989 | this.filterPeaking.type = "peaking";
1990 | this.filterBp.type = "bandpass";
1991 | this.filterPeaking.frequency.value = 100;
1992 | this.filterPeaking.gain.value = 20;
1993 | this.filterPeaking.Q.value = 5;
1994 | this.filterBp.frequency.value = 100;
1995 | this.filterBp.Q.value = 1;
1996 | }
1997 | }
1998 | });
1999 |
2000 | Tuna.prototype.EnvelopeFollower = function(properties) {
2001 | if (!properties) {
2002 | properties = this.getDefaults();
2003 | }
2004 | this.input = userContext.createGain();
2005 | this.jsNode = this.output = userContext.createScriptProcessor(this.buffersize, 1, 1);
2006 |
2007 | this.input.connect(this.output);
2008 |
2009 | this.attackTime = initValue(properties.attackTime, this.defaults.attackTime.value);
2010 | this.releaseTime = initValue(properties.releaseTime, this.defaults.releaseTime.value);
2011 | this._envelope = 0;
2012 | this.target = properties.target || {};
2013 | this.callback = properties.callback || function() {};
2014 |
2015 | this.bypass = properties.bypass || this.defaults.bypass.value;
2016 | };
2017 | Tuna.prototype.EnvelopeFollower.prototype = Object.create(Super, {
2018 | name: {
2019 | value: "EnvelopeFollower"
2020 | },
2021 | defaults: {
2022 | value: {
2023 | attackTime: {
2024 | value: 0.003,
2025 | min: 0,
2026 | max: 0.5,
2027 | automatable: false,
2028 | type: FLOAT
2029 | },
2030 | releaseTime: {
2031 | value: 0.5,
2032 | min: 0,
2033 | max: 0.5,
2034 | automatable: false,
2035 | type: FLOAT
2036 | },
2037 | bypass: {
2038 | value: false,
2039 | automatable: false,
2040 | type: BOOLEAN
2041 | }
2042 | }
2043 | },
2044 | buffersize: {
2045 | value: 256
2046 | },
2047 | envelope: {
2048 | value: 0
2049 | },
2050 | sampleRate: {
2051 | value: 44100
2052 | },
2053 | attackTime: {
2054 | enumerable: true,
2055 | get: function() {
2056 | return this._attackTime;
2057 | },
2058 | set: function(value) {
2059 | this._attackTime = value;
2060 | this._attackC = Math.exp(-1 / this._attackTime * this.sampleRate / this.buffersize);
2061 | }
2062 | },
2063 | releaseTime: {
2064 | enumerable: true,
2065 | get: function() {
2066 | return this._releaseTime;
2067 | },
2068 | set: function(value) {
2069 | this._releaseTime = value;
2070 | this._releaseC = Math.exp(-1 / this._releaseTime * this.sampleRate / this.buffersize);
2071 | }
2072 | },
2073 | callback: {
2074 | get: function() {
2075 | return this._callback;
2076 | },
2077 | set: function(value) {
2078 | if (typeof value === "function") {
2079 | this._callback = value;
2080 | } else {
2081 | console.error("tuna.js: " + this.name + ": Callback must be a function!");
2082 | }
2083 | }
2084 | },
2085 | target: {
2086 | get: function() {
2087 | return this._target;
2088 | },
2089 | set: function(value) {
2090 | this._target = value;
2091 | }
2092 | },
2093 | activate: {
2094 | value: function(doActivate) {
2095 | this.activated = doActivate;
2096 | if (doActivate) {
2097 | this.jsNode.connect(userContext.destination);
2098 | this.jsNode.onaudioprocess = this.returnCompute(this);
2099 | } else {
2100 | this.jsNode.disconnect();
2101 | this.jsNode.onaudioprocess = null;
2102 | }
2103 | if (this.activateCallback) {
2104 | this.activateCallback(doActivate);
2105 | }
2106 | }
2107 | },
2108 | returnCompute: {
2109 | value: function(instance) {
2110 | return function(event) {
2111 | instance.compute(event);
2112 | };
2113 | }
2114 | },
2115 | compute: {
2116 | value: function(event) {
2117 | var count = event.inputBuffer.getChannelData(0).length,
2118 | channels = event.inputBuffer.numberOfChannels,
2119 | current, chan, rms, i;
2120 | chan = rms = i = 0;
2121 |
2122 | for(chan = 0; chan < channels; ++chan) {
2123 | for (i = 0; i < count; ++i) {
2124 | current = event.inputBuffer.getChannelData(chan)[i];
2125 | rms += (current * current);
2126 | }
2127 | }
2128 | rms = Math.sqrt(rms / channels);
2129 |
2130 | if (this._envelope < rms) {
2131 | this._envelope *= this._attackC;
2132 | this._envelope += (1 - this._attackC) * rms;
2133 | } else {
2134 | this._envelope *= this._releaseC;
2135 | this._envelope += (1 - this._releaseC) * rms;
2136 | }
2137 | this._callback(this._target, this._envelope);
2138 | }
2139 | }
2140 | });
2141 |
2142 | Tuna.prototype.LFO = function(properties) {
2143 | if (!properties) {
2144 | properties = this.getDefaults();
2145 | }
2146 |
2147 | //Instantiate AudioNode
2148 | this.input = userContext.createGain();
2149 | this.output = userContext.createScriptProcessor(256, 1, 1);
2150 | this.activateNode = userContext.destination;
2151 |
2152 | //Set Properties
2153 | this.frequency = initValue(properties.frequency, this.defaults.frequency.value);
2154 | this.offset = initValue(properties.offset, this.defaults.offset.value);
2155 | this.oscillation = initValue(properties.oscillation, this.defaults.oscillation.value);
2156 | this.phase = initValue(properties.phase, this.defaults.phase.value);
2157 | this.target = properties.target || {};
2158 | this.output.onaudioprocess = this.callback(properties.callback || function() {});
2159 | this.bypass = properties.bypass || this.defaults.bypass.value;
2160 | };
2161 | Tuna.prototype.LFO.prototype = Object.create(Super, {
2162 | name: {
2163 | value: "LFO"
2164 | },
2165 | bufferSize: {
2166 | value: 256
2167 | },
2168 | sampleRate: {
2169 | value: 44100
2170 | },
2171 | defaults: {
2172 | value: {
2173 | frequency: {
2174 | value: 1,
2175 | min: 0,
2176 | max: 20,
2177 | automatable: false,
2178 | type: FLOAT
2179 | },
2180 | offset: {
2181 | value: 0.85,
2182 | min: 0,
2183 | max: 22049,
2184 | automatable: false,
2185 | type: FLOAT
2186 | },
2187 | oscillation: {
2188 | value: 0.3,
2189 | min: -22050,
2190 | max: 22050,
2191 | automatable: false,
2192 | type: FLOAT
2193 | },
2194 | phase: {
2195 | value: 0,
2196 | min: 0,
2197 | max: 2 * Math.PI,
2198 | automatable: false,
2199 | type: FLOAT
2200 | },
2201 | bypass: {
2202 | value: false,
2203 | automatable: false,
2204 | type: BOOLEAN
2205 | }
2206 | }
2207 | },
2208 | frequency: {
2209 | get: function() {
2210 | return this._frequency;
2211 | },
2212 | set: function(value) {
2213 | this._frequency = value;
2214 | this._phaseInc = 2 * Math.PI * this._frequency * this.bufferSize / this.sampleRate;
2215 | }
2216 | },
2217 | offset: {
2218 | get: function() {
2219 | return this._offset;
2220 | },
2221 | set: function(value) {
2222 | this._offset = value;
2223 | }
2224 | },
2225 | oscillation: {
2226 | get: function() {
2227 | return this._oscillation;
2228 | },
2229 | set: function(value) {
2230 | this._oscillation = value;
2231 | }
2232 | },
2233 | phase: {
2234 | get: function() {
2235 | return this._phase;
2236 | },
2237 | set: function(value) {
2238 | this._phase = value;
2239 | }
2240 | },
2241 | target: {
2242 | get: function() {
2243 | return this._target;
2244 | },
2245 | set: function(value) {
2246 | this._target = value;
2247 | }
2248 | },
2249 | activate: {
2250 | value: function(doActivate) {
2251 | if (doActivate) {
2252 | this.output.connect(userContext.destination);
2253 | if (this.activateCallback) {
2254 | this.activateCallback(doActivate);
2255 | }
2256 | } else {
2257 | this.output.disconnect();
2258 | }
2259 | }
2260 | },
2261 | callback: {
2262 | value: function(callback) {
2263 | var that = this;
2264 | return function() {
2265 | that._phase += that._phaseInc;
2266 | if (that._phase > 2 * Math.PI) {
2267 | that._phase = 0;
2268 | }
2269 | callback(that._target, that._offset + that._oscillation * Math.sin(that._phase));
2270 | };
2271 | }
2272 | }
2273 | });
2274 |
2275 | Tuna.toString = Tuna.prototype.toString = function() {
2276 | return "Please visit https://github.com/Theodeus/tuna/wiki for instructions on how to use Tuna.js";
2277 | };
2278 | })();
2279 |
--------------------------------------------------------------------------------