├── .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 | tuna, tuna, tuna 7 | 8 | Effect list: 9 | ==== 10 | 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 | --------------------------------------------------------------------------------