├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── package.json ├── presets ├── 0.json ├── 1.json └── 2.json ├── server.js ├── static ├── javascripts │ ├── app.js │ ├── chat.js │ ├── freesound.js │ ├── impulse-response.js │ ├── jquery.knob.min.js │ ├── kit.js │ ├── search.js │ ├── socket.io.js │ └── wave.js ├── sounds │ ├── drum-samples │ │ └── TR808 │ │ │ ├── hihat.mp3 │ │ │ ├── hihat.ogg │ │ │ ├── kick.mp3 │ │ │ ├── kick.ogg │ │ │ ├── snare.mp3 │ │ │ └── snare.ogg │ └── impulse-responses │ │ ├── irHall.ogg │ │ └── matrix-reverb2.wav └── stylesheets │ └── styles.css └── views ├── home.ejs └── index.ejs /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | README.md 4 | docker-compose.yml 5 | node_modules 6 | npm-debug.log 7 | CODING_RULES 8 | LICENSE 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | 44 | package-lock.json 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:carbon 2 | 3 | ADD . /src 4 | WORKDIR /src 5 | RUN npm install 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi Web Audio Step Sequencer 2 | A web collaborative step sequencer using [Freesound](https://freesound.org/) content. 3 | Built using Express.js, Web Audio API, Socket.io. 4 | 5 | The sequencer is available [here](https://labs.freesound.org/sequencer/). 6 | 7 | Step sequencer inspired by [Catarak Web Audio Sequencer](https://github.com/catarak/web-audio-sequencer). 8 | 9 | 10 | INSTALL (Docker) 11 | ------------------- 12 | Note: You'll need to have [`docker`](https://docs.docker.com/install/) and [`docker-compose`](https://docs.docker.com/compose/install/) installed. 13 | 14 | ``` 15 | docker-compose up 16 | ``` 17 | 18 | INSTALL 19 | ------------------- 20 | 21 | ``` 22 | npm install 23 | npm install nodemon 24 | npm start 25 | ``` 26 | 27 | 28 | DEPLOY 29 | ------------------- 30 | The server is looking for the environment variables `MULT_WEB_SEQ_SERV`, `MULT_WEB_SEQ_SERV_P` and `BASE_PATH`. If undefined, they are set to *localhost*, *8080* and *""*. 31 | Setting `NODE_ENV` to production gives the best performance. 32 | 33 | - `MULT_WEB_SEQ_SERV`: The server where Node (or Docker) starts. 34 | - `MULT_WEB_SEQ_SERV_P`: The port to which the Node server is listening. 35 | - `BASE_PATH`: Base path which will be added to all the request links to the app. 36 | 37 | Example for starting the app on your sever using npm: 38 | ``` 39 | export MULT_WEB_SEQ_SERV=myserver.com 40 | export MULT_WEB_SEQ_SERV_P=9050 41 | export NODE_ENV=production 42 | node server.js 43 | ``` 44 | 45 | If you want to use Docker for the deployment, you will have to edit the docker-compose.yml file according to your settings. 46 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | web: 2 | build: . 3 | command: npm start 4 | ports: 5 | - "8080:8080" 6 | volumes: 7 | - .:/src 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-web-audio-sequencer", 3 | "version": "0.1.0", 4 | "description": "Multi-user step sequencer using Freesound content", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon server.js" 9 | }, 10 | "author": "Xavier Favory , Valentin Nerin, Nicolas Derouineau", 11 | "license": "GPL-3.0", 12 | "dependencies": { 13 | "body-parser": "^1.18.2", 14 | "bootstrap-slider": "^10.0.0", 15 | "cookie-parser": "^1.4.3", 16 | "cookie-session": "^1.3.2", 17 | "cookieparser": "^0.1.0", 18 | "ejs": "^2.5.7", 19 | "express": "^4.16.2", 20 | "express-session": "^1.15.6", 21 | "express-socket.io-session": "^1.3.2", 22 | "method-override": "^2.3.10", 23 | "nodemon": "^1.14.11", 24 | "session-file-store": "^1.1.2", 25 | "session.socket.io": "^0.2.1", 26 | "socket.io": "^2.0.4", 27 | "socket.io-express-session": "^0.1.3" 28 | }, 29 | "devDependencies": {}, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/xavierfav/multi-web-audio-sequencer.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/xavierfav/multi-web-audio-sequencer/issues" 36 | }, 37 | "homepage": "https://github.com/xavierfav/multi-web-audio-sequencer#readme", 38 | "description": "INSTALL\r -------------------" 39 | } 40 | -------------------------------------------------------------------------------- /presets/0.json: -------------------------------------------------------------------------------- 1 | {"sequenceLength":"32","tempo":88,"trackNames":["snare","hihat","guitar","loop","voice","voice","loop","voice"],"pads":[[0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"sounds":["https://freesound.org/data/previews/104/104241_1531859-lq.ogg","https://freesound.org/data/previews/44/44859_177850-lq.ogg","https://freesound.org/data/previews/17/17699_65446-lq.ogg","https://freesound.org/data/previews/167/167726_443790-lq.ogg","https://freesound.org/data/previews/31/31716_92661-lq.ogg","https://freesound.org/data/previews/31/31708_92661-lq.ogg","https://freesound.org/data/previews/22/22126_109943-lq.ogg","https://freesound.org/data/previews/279/279897_2050105-lq.ogg"],"waves":[[false,false],[false,false],[false,false],[false,false],[false,false],[false,false],[false,false],[0,3.1741510954501706]],"gains":[-9,-2,-6,-8,-3,-4,-8,3]} 2 | -------------------------------------------------------------------------------- /presets/1.json: -------------------------------------------------------------------------------- 1 | {"sequenceLength":"64","tempo":60,"trackNames":["loop","Drum","Snare","fxClick","cata","tamb","loop"],"pads":[[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,1,0,0,0,1,0,1,0,1,0,1,0,0,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1],[0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],[0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"sounds":["https://freesound.org/data/previews/214/214416_462105-lq.ogg","https://freesound.org/data/previews/347/347625_1126957-lq.ogg","https://freesound.org/data/previews/38/38934_14771-lq.ogg","https://freesound.org/data/previews/108/108335_1855536-lq.ogg","https://freesound.org/data/previews/237/237211_4280272-lq.ogg","https://freesound.org/data/previews/348/348073_6165769-lq.ogg","https://freesound.org/data/previews/18/18550_74084-lq.ogg"],"waves":[[false,false],[false,false],[false,false],[false,false],[0.12227827323959074,1.4106122448979592],[false,false],[false,false]],"gains":[5,-11,-6,-6,-16,-6,-19]} 2 | -------------------------------------------------------------------------------- /presets/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sequenceLength": "64", 3 | "tempo": 88, 4 | "trackNames": [ 5 | "kick", 6 | "snare", 7 | "skank 1", 8 | "LaLa", 9 | "Skank 2", 10 | "Lala2" 11 | ], 12 | "pads": [ 13 | [ 14 | 1, 15 | 0, 16 | 0, 17 | 0, 18 | 1, 19 | 0, 20 | 0, 21 | 0, 22 | 1, 23 | 0, 24 | 0, 25 | 0, 26 | 1, 27 | 0, 28 | 0, 29 | 0, 30 | 1, 31 | 0, 32 | 0, 33 | 0, 34 | 1, 35 | 0, 36 | 0, 37 | 0, 38 | 1, 39 | 0, 40 | 0, 41 | 0, 42 | 1, 43 | 0, 44 | 0, 45 | 0, 46 | 1, 47 | 0, 48 | 0, 49 | 0, 50 | 1, 51 | 0, 52 | 0, 53 | 0, 54 | 1, 55 | 0, 56 | 0, 57 | 0, 58 | 1, 59 | 0, 60 | 0, 61 | 0, 62 | 1, 63 | 0, 64 | 0, 65 | 0, 66 | 1, 67 | 0, 68 | 0, 69 | 0, 70 | 1, 71 | 0, 72 | 0, 73 | 0, 74 | 1, 75 | 0, 76 | 0, 77 | 0 78 | ], 79 | [ 80 | 0, 81 | 0, 82 | 0, 83 | 0, 84 | 0, 85 | 0, 86 | 0, 87 | 0, 88 | 0, 89 | 0, 90 | 0, 91 | 0, 92 | 0, 93 | 0, 94 | 0, 95 | 0, 96 | 0, 97 | 0, 98 | 0, 99 | 0, 100 | 0, 101 | 0, 102 | 0, 103 | 0, 104 | 0, 105 | 0, 106 | 0, 107 | 0, 108 | 0, 109 | 0, 110 | 0, 111 | 0, 112 | 0, 113 | 0, 114 | 0, 115 | 0, 116 | 1, 117 | 0, 118 | 0, 119 | 0, 120 | 0, 121 | 0, 122 | 0, 123 | 0, 124 | 1, 125 | 0, 126 | 0, 127 | 0, 128 | 0, 129 | 0, 130 | 0, 131 | 0, 132 | 1, 133 | 0, 134 | 0, 135 | 0, 136 | 0, 137 | 0, 138 | 0, 139 | 0, 140 | 0, 141 | 1, 142 | 1, 143 | 0 144 | ], 145 | [ 146 | 0, 147 | 0, 148 | 1, 149 | 0, 150 | 0, 151 | 0, 152 | 1, 153 | 0, 154 | 0, 155 | 0, 156 | 1, 157 | 0, 158 | 0, 159 | 0, 160 | 1, 161 | 0, 162 | 0, 163 | 0, 164 | 0, 165 | 0, 166 | 0, 167 | 0, 168 | 0, 169 | 0, 170 | 0, 171 | 0, 172 | 0, 173 | 0, 174 | 0, 175 | 0, 176 | 0, 177 | 0, 178 | 0, 179 | 0, 180 | 1, 181 | 0, 182 | 0, 183 | 0, 184 | 1, 185 | 0, 186 | 0, 187 | 0, 188 | 1, 189 | 0, 190 | 0, 191 | 0, 192 | 1, 193 | 0, 194 | 0, 195 | 0, 196 | 0, 197 | 0, 198 | 0, 199 | 0, 200 | 0, 201 | 0, 202 | 0, 203 | 0, 204 | 0, 205 | 0, 206 | 0, 207 | 0, 208 | 0, 209 | 0 210 | ], 211 | [ 212 | 0, 213 | 0, 214 | 0, 215 | 0, 216 | 0, 217 | 0, 218 | 0, 219 | 0, 220 | 0, 221 | 0, 222 | 0, 223 | 0, 224 | 0, 225 | 0, 226 | 0, 227 | 0, 228 | 0, 229 | 0, 230 | 0, 231 | 0, 232 | 0, 233 | 0, 234 | 0, 235 | 0, 236 | 0, 237 | 0, 238 | 0, 239 | 0, 240 | 0, 241 | 0, 242 | 0, 243 | 0, 244 | 0, 245 | 0, 246 | 0, 247 | 0, 248 | 1, 249 | 0, 250 | 0, 251 | 0, 252 | 0, 253 | 0, 254 | 0, 255 | 0, 256 | 0, 257 | 0, 258 | 0, 259 | 0, 260 | 0, 261 | 0, 262 | 0, 263 | 0, 264 | 0, 265 | 0, 266 | 0, 267 | 0, 268 | 0, 269 | 0, 270 | 0, 271 | 0, 272 | 0, 273 | 0, 274 | 0, 275 | 0 276 | ], 277 | [ 278 | 0, 279 | 0, 280 | 0, 281 | 0, 282 | 0, 283 | 0, 284 | 0, 285 | 0, 286 | 0, 287 | 0, 288 | 0, 289 | 0, 290 | 0, 291 | 0, 292 | 0, 293 | 0, 294 | 0, 295 | 0, 296 | 1, 297 | 0, 298 | 0, 299 | 0, 300 | 1, 301 | 0, 302 | 0, 303 | 0, 304 | 1, 305 | 0, 306 | 0, 307 | 0, 308 | 1, 309 | 0, 310 | 0, 311 | 0, 312 | 0, 313 | 0, 314 | 0, 315 | 0, 316 | 0, 317 | 0, 318 | 0, 319 | 0, 320 | 0, 321 | 0, 322 | 0, 323 | 0, 324 | 0, 325 | 0, 326 | 0, 327 | 0, 328 | 1, 329 | 0, 330 | 0, 331 | 0, 332 | 1, 333 | 0, 334 | 0, 335 | 0, 336 | 1, 337 | 0, 338 | 0, 339 | 0, 340 | 1, 341 | 0 342 | ], 343 | [ 344 | 0, 345 | 0, 346 | 0, 347 | 0, 348 | 0, 349 | 0, 350 | 0, 351 | 0, 352 | 0, 353 | 0, 354 | 0, 355 | 0, 356 | 0, 357 | 0, 358 | 0, 359 | 0, 360 | 0, 361 | 0, 362 | 0, 363 | 0, 364 | 0, 365 | 0, 366 | 0, 367 | 0, 368 | 0, 369 | 0, 370 | 0, 371 | 0, 372 | 0, 373 | 0, 374 | 0, 375 | 0, 376 | 0, 377 | 0, 378 | 0, 379 | 0, 380 | 0, 381 | 0, 382 | 0, 383 | 0, 384 | 0, 385 | 0, 386 | 0, 387 | 0, 388 | 0, 389 | 0, 390 | 0, 391 | 0, 392 | 0, 393 | 0, 394 | 0, 395 | 0, 396 | 0, 397 | 0, 398 | 0, 399 | 1, 400 | 0, 401 | 0, 402 | 0, 403 | 0, 404 | 0, 405 | 0, 406 | 0, 407 | 0 408 | ] 409 | ], 410 | "sounds": [ 411 | "/sequencer/assets/sounds/drum-samples/TR808/kick.ogg", 412 | "/sequencer/assets/sounds/drum-samples/TR808/snare.ogg", 413 | "https://freesound.org/data/previews/55/55713_692344-lq.ogg", 414 | "https://freesound.org/data/previews/183/183145_2140699-lq.ogg", 415 | "https://freesound.org/data/previews/55/55696_692344-lq.ogg", 416 | "https://freesound.org/data/previews/243/243876_3028938-lq.ogg" 417 | ], 418 | "waves": [ 419 | [ 420 | false, 421 | false 422 | ], 423 | [ 424 | false, 425 | false 426 | ], 427 | [ 428 | false, 429 | false 430 | ], 431 | [ 432 | false, 433 | false 434 | ], 435 | [ 436 | false, 437 | false 438 | ], 439 | [ 440 | false, 441 | false 442 | ] 443 | ], 444 | "gains": [ 445 | -2, 446 | -6, 447 | -7, 448 | -6, 449 | -6, 450 | -6 451 | ] 452 | } 453 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var session = require('express-session')({ 4 | secret: process.env.SESSION_SECRET || "azaezaedzadzea", 5 | resave: true, 6 | saveUninitialized: true 7 | }); 8 | var sharedsession = require("express-socket.io-session"); 9 | var http = require('http').Server(app); 10 | var bodyParser = require('body-parser'); 11 | var eventEmitter = require('events').EventEmitter 12 | var hostname = process.env.MULT_WEB_SEQ_SERV || 'localhost'; 13 | var base_path = process.env.BASE_PATH || ''; 14 | var hostnamePort = process.env.MULT_WEB_SEQ_SERV_P || '8080'; 15 | var adminPassword = process.env.ADMIN_PASSWORD || 'admin'; 16 | var freesoundClientToken = process.env.FREESOUND_TOKEN || 'bs5DQrWNL9d8zrQl0ApCvcQqwg0gg8ytGE60qg5o'; 17 | var io = require('socket.io')(http, {path: base_path + '/socket.io'}); 18 | var fs = require('fs'); 19 | 20 | var fullservername = hostname + ':' + hostnamePort; 21 | var rooms = ["1", "2", "3", "4", "5", "6", "7", "8"]; 22 | var roomUsers = [[], [], [], [], [], [], [], []]; 23 | var roomLastConnections = [null, null, null, null, null, null, null, null]; 24 | 25 | var sequencerState = { 26 | sequenceLength: 16, 27 | tempo: 120, 28 | trackNames: ['kick', 'snare', 'hihat'], 29 | pads: [ 30 | Array(64).fill(0), 31 | Array(64).fill(0), 32 | Array(64).fill(0) 33 | ], 34 | sounds: [ 35 | base_path + '/assets/sounds/drum-samples/TR808/kick.ogg', 36 | base_path + '/assets/sounds/drum-samples/TR808/snare.ogg', 37 | base_path + '/assets/sounds/drum-samples/TR808/hihat.ogg' 38 | ], 39 | waves: [ 40 | [false, false], 41 | [false, false], 42 | [false, false] 43 | ], 44 | gains: [-6, -6, -6] 45 | }; 46 | 47 | var sequencerPresetFiles = []; 48 | getListPresetFiles(); 49 | 50 | var sequencerStates = [JSON.parse(JSON.stringify(sequencerState)), 51 | JSON.parse(JSON.stringify(sequencerState)), 52 | JSON.parse(JSON.stringify(sequencerState)), 53 | JSON.parse(JSON.stringify(sequencerState)), 54 | JSON.parse(JSON.stringify(sequencerState)), 55 | JSON.parse(JSON.stringify(sequencerState)), 56 | JSON.parse(JSON.stringify(sequencerState)), 57 | JSON.parse(JSON.stringify(sequencerState)), 58 | ]; 59 | 60 | 61 | //moteur de template 62 | app.set('view engine', 'ejs'); 63 | 64 | //middleware 65 | app.use(session); 66 | app.use(base_path + '/assets', express.static(__dirname + '/static')); 67 | app.use(base_path + '/assets', express.static(__dirname + '/node_modules')); 68 | io.use(sharedsession(session, { 69 | autoSave: true 70 | })); 71 | app.set('trust proxy', true) 72 | 73 | 74 | // ON CONNECTION CONNECT TO ROOM AND SEND STATE TO CLIENT 75 | io.sockets.on('connection', function (socket) { 76 | socket.on('room', function (room) { 77 | room--; 78 | console.log("New client connected to room: " + room); 79 | socket.join(room); 80 | socket.chatRoom = null; 81 | 82 | // store connection activity 83 | roomLastConnections[room] = new Date(); 84 | 85 | // if username in session, autolog tu chat 86 | socket.username = socket.handshake.session.username; 87 | if (socket.username != null) { 88 | socket.chatRoom = room; 89 | roomUsers[room].push(socket.username); 90 | socket.emit('autoLogin', { 91 | numUsers: roomUsers[room].length, 92 | username: socket.username 93 | }); 94 | // echo globally (all clients) that a person has connected 95 | socket.in(room).broadcast.emit('user joined', { 96 | username: socket.username, 97 | numUsers: roomUsers[room].length 98 | }); 99 | } 100 | 101 | // send state 102 | socket.emit('SendCurrentState', sequencerStates[room]); 103 | 104 | // send preset names 105 | for (i = 0; i < sequencerPresetFiles.length; i++) { 106 | socket.emit('sendSaveSequencerPreset', sequencerPresetFiles[i].split('.')[0]); 107 | } 108 | 109 | // PAD RECEPTION VIA THE CLIENT 110 | socket.on('pad', function (message) { 111 | console.log('receive pad change: ' + message); 112 | socket.in(room).broadcast.emit('sendPad', message); 113 | var trackId = message[0]; 114 | var padId = message[1]; 115 | var padState = message[2]; 116 | sequencerStates[room]['pads'][trackId][padId] = padState; 117 | }); 118 | 119 | // TEMPO RECEPTION VIA THE CLIENT 120 | socket.on('tempo', function (message) { 121 | console.log('receive tempo change: ' + message); 122 | socket.in(room).broadcast.emit('tempo', message); 123 | var tempo = message; 124 | sequencerStates[room].tempo = tempo; 125 | }); 126 | 127 | // NEW TRACK 128 | socket.on('newTrack', function (message) { 129 | console.log('receive new track: ' + message); 130 | var trackName = message[0]; 131 | var soundUrl = message[1]; 132 | var trackId = sequencerStates[room].trackNames.length; 133 | message.unshift(trackId); 134 | io.sockets.in(room).emit('sendNewTrack', message); 135 | sequencerStates[room].trackNames[trackId] = trackName; 136 | sequencerStates[room].pads[trackId] = Array(64).fill(0); 137 | sequencerStates[room].sounds[trackId] = soundUrl; 138 | sequencerStates[room].waves[trackId] = [false, false]; 139 | sequencerStates[room].gains[trackId] = -6; 140 | }); 141 | 142 | // LOAD SOUND INTO A TRACK 143 | socket.on('loadSound', function (message) { 144 | console.log('receive load sound: ' + message); 145 | socket.in(room).broadcast.emit('sendLoadSound', message); 146 | var trackId = message[0]; 147 | var soundUrl = message[1]; 148 | sequencerStates[room].sounds[trackId] = soundUrl; 149 | }); 150 | 151 | // DELETE TRACK 152 | socket.on('deleteTrack', function (message) { 153 | console.log('receive delete track: ' + message); 154 | io.sockets.in(room).emit('sendDeleteTrack', message); 155 | var trackId = message; 156 | sequencerStates[room].trackNames.splice(trackId, 1); 157 | sequencerStates[room].sounds.splice(trackId, 1); 158 | sequencerStates[room].pads.splice(trackId, 1); 159 | sequencerStates[room].waves.splice(trackId, 1); 160 | sequencerStates[room].gains.splice(trackId, 1); 161 | }); 162 | 163 | // CHANGE WAVE REGION 164 | socket.on('waveRegion', function (message) { 165 | var trackId = message[0]; 166 | console.log('receive change wave region: ' + trackId); 167 | socket.in(room).broadcast.emit('sendWaveRegion', message); 168 | sequencerStates[room].waves[trackId] = [message[1], message[2]]; 169 | }); 170 | 171 | // CHANGE LENGTH SEQUENCE 172 | socket.on('sequenceLength', function (message) { 173 | console.log('receive change senquence length: ' + message); 174 | sequencerStates[room].sequenceLength = message; 175 | socket.in(room).broadcast.emit('sendSequenceLength', message); 176 | }); 177 | 178 | // CHANGE TRACK GAIN 179 | socket.on('gain', function (message) { 180 | console.log('receive change gain: ' + message); 181 | sequencerStates[room].gains[message[0]] = message[1]; 182 | socket.in(room).broadcast.emit('sendGain', message); 183 | }); 184 | 185 | // SAVE PRESET 186 | socket.on('savePreset', function (message) { 187 | console.log('receive save preset'); 188 | var presetName = message[1]; 189 | var sequencerPresetState = JSON.parse(message[0]); 190 | savePreset(presetName, sequencerPresetState) 191 | io.sockets.in(room).emit('sendSaveSequencerPreset', presetName); 192 | }); 193 | 194 | socket.on('loadPreset', function (message) { 195 | console.log('receive load preset'); 196 | var preset = getPreset(message); 197 | sequencerStates[room] = preset; 198 | io.sockets.in(room).emit('sendSequencerPreset', JSON.stringify(preset)); 199 | }); 200 | 201 | // CHAT 202 | // when the client emits 'new message', this listens and executes 203 | socket.on('new message', function (data) { 204 | // we tell the client to execute 'new message' 205 | socket.in(room).broadcast.emit('new message', { 206 | username: socket.username, 207 | message: data 208 | }); 209 | }); 210 | 211 | // when the client emits 'add user', this listens and executes 212 | socket.on('add user', function (username) { 213 | if (socket.chatRoom != null) { 214 | console.log("Client change name on room: " + room); 215 | socket.in(room).broadcast.emit('user change name', { 216 | oldName: socket.username, 217 | newName: username 218 | }); 219 | var index = roomUsers[socket.chatRoom].indexOf(socket.username); 220 | if (index > -1) { 221 | roomUsers[room].splice(index, 1); 222 | } 223 | socket.username = username; 224 | roomUsers[room].push(username); 225 | } else { 226 | socket.chatRoom = room; 227 | console.log("New client on the chat: " + room); 228 | roomUsers[room].push(username); 229 | // we store the username in the socket session for this client 230 | socket.username = username; 231 | socket.emit('login', { 232 | numUsers: roomUsers[room].length 233 | }); 234 | // echo globally (all clients) that a person has connected 235 | socket.in(room).broadcast.emit('user joined', { 236 | username: socket.username, 237 | numUsers: roomUsers[room].length 238 | }); 239 | } 240 | socket.handshake.session.username = username; 241 | socket.handshake.session.save(); 242 | }); 243 | 244 | // when the client emits 'typing', we broadcast it to others 245 | socket.on('typing', function () { 246 | socket.in(room).broadcast.emit('typing', { 247 | username: socket.username 248 | }); 249 | }); 250 | 251 | // when the client emits 'stop typing', we broadcast it to others 252 | socket.on('stop typing', function () { 253 | socket.in(room).broadcast.emit('stop typing', { 254 | username: socket.username 255 | }); 256 | }); 257 | 258 | // when the user disconnects.. perform this 259 | socket.on('disconnect', function () { 260 | if (socket.chatRoom != null) { 261 | console.log("A client left the chat of room: " + socket.chatRoom); 262 | // echo globally that this client has left 263 | socket.in(socket.chatRoom).broadcast.emit('user left', { 264 | username: socket.username, 265 | numUsers: roomUsers[socket.chatRoom].length - 1 266 | }); 267 | // delete user from roomUsers lists 268 | var index = roomUsers[socket.chatRoom].indexOf(socket.username); 269 | if (index > -1) { 270 | roomUsers[room].splice(index, 1); 271 | } 272 | } 273 | }); 274 | }); 275 | }); 276 | 277 | 278 | // PRESETS 279 | function getListPresetFiles() { 280 | fs.readdir('./presets/', function(err, items) { 281 | console.log("Existing presets: " + items); 282 | sequencerPresetFiles = items; 283 | }); 284 | } 285 | 286 | function getPreset(presetId) { 287 | var preset = require('./presets/' + sequencerPresetFiles[presetId]); 288 | return require('./presets/' + sequencerPresetFiles[presetId]); 289 | } 290 | 291 | function savePreset(presetName, preset) { 292 | var jsonPreset = JSON.stringify(preset); 293 | fs.writeFile('./presets/' + presetName + '.json', jsonPreset, 'utf8'); 294 | sequencerPresetFiles.push(presetName + '.json'); 295 | } 296 | 297 | 298 | // ACTIVITY DATE 299 | function updateActivity(datetime) { 300 | var theevent = new Date(datetime); 301 | now = new Date(); 302 | var sec_num = (now - theevent) / 1000; 303 | var days = Math.floor(sec_num / (3600 * 24)); 304 | var hours = Math.floor((sec_num - (days * (3600 * 24))) / 3600); 305 | var minutes = Math.floor((sec_num - (days * (3600 * 24)) - (hours * 3600)) / 60); 306 | var seconds = Math.floor(sec_num - (days * (3600 * 24)) - (hours * 3600) - (minutes * 60)); 307 | 308 | if (hours < 10) { 309 | hours = "0" + hours; 310 | } 311 | if (minutes < 10) { 312 | minutes = "0" + minutes; 313 | } 314 | if (seconds < 10) { 315 | seconds = "0" + seconds; 316 | } 317 | 318 | if (days > 1000) { 319 | return '-' 320 | } else if (days > 30) { 321 | return 'more than 30 days ago' 322 | } else { 323 | return days + ' days ' + hours + ' hours ' + minutes + ' min ' + seconds + ' s '; 324 | } 325 | } 326 | 327 | // VIEWS 328 | app.get(base_path + '/', (req, res) => { 329 | var room = req.query.room; 330 | var adminClient = req.query.admin == adminPassword; 331 | var roomInt = parseInt(room) 332 | if (Number.isInteger(roomInt) && roomInt > 0 && roomInt < 9) { 333 | res.render('index.ejs', { 334 | room: room, 335 | adminClient: adminClient, 336 | base_path: base_path 337 | }); 338 | } else { 339 | var dateNow = new Date(); 340 | var roomConnectionsAgo = roomLastConnections.map(function (e) { 341 | var time = updateActivity(e) 342 | if (time < 0) { 343 | time = 0; 344 | } 345 | return updateActivity(e) 346 | }); 347 | res.render('home.ejs', { 348 | fullservername: fullservername, 349 | roomUsers: roomUsers, 350 | roomConnectionsAgo: roomConnectionsAgo, 351 | base_path: base_path 352 | }); 353 | } 354 | }); 355 | 356 | 357 | // AJAX 358 | app.get(base_path + '/get_freesound_token', function(req, res){ 359 | res.send(freesoundClientToken); 360 | }); 361 | 362 | 363 | http.listen(hostnamePort, function () { 364 | console.log('connecté sur le', fullservername); 365 | }); 366 | -------------------------------------------------------------------------------- /static/javascripts/app.js: -------------------------------------------------------------------------------- 1 | //audio node variables 2 | var context; 3 | var convolver; 4 | var compressor; 5 | var masterGainNode; 6 | var effectLevelNode; 7 | var lowPassFilterNode; 8 | var mediaRecorder; 9 | var recordingDest; 10 | var chunks = []; 11 | 12 | var noteTime; 13 | var startTime; 14 | var lastDrawTime = -1; 15 | var rhythmIndex = 0; 16 | var timeoutId; 17 | var testBuffer = null; 18 | var isRecording = false; 19 | var isPlaying = false; 20 | 21 | var currentSequencerState = null; 22 | var currentKit = null; 23 | var wave = null; 24 | var reverbImpulseResponse = null; 25 | var sequencerPresetNames = []; 26 | 27 | var tempo = 120; 28 | var TEMPO_MAX = 200; 29 | var TEMPO_MIN = 40; 30 | var TEMPO_STEP = 1; 31 | var MAXLENGTH = 64; 32 | var COMPRESSOR_ACTIVATED = false; 33 | 34 | 35 | if (window.hasOwnProperty('AudioContext') && !window.hasOwnProperty('webkitAudioContext')) { 36 | window.webkitAudioContext = AudioContext; 37 | } 38 | 39 | $(function () { 40 | init(); 41 | addNewTrackEvent(); 42 | addChangeSequenceLengthEvent(); 43 | playPauseListener(); 44 | RecordListener(); 45 | lowPassFilterListener(); 46 | reverbListener(); 47 | createLowPassFilterSliders(); 48 | initializeTempo(); 49 | changeTempoListener(); 50 | search = initSearch(); 51 | }); 52 | 53 | function createLowPassFilterSliders() { 54 | $("#freq-slider").slider({ 55 | value: 1, 56 | min: 0, 57 | max: 1, 58 | step: 0.01, 59 | disabled: true, 60 | slide: changeFrequency 61 | }); 62 | $("#quality-slider").slider({ 63 | value: 0, 64 | min: 0, 65 | max: 1, 66 | step: 0.01, 67 | disabled: true, 68 | slide: changeQuality 69 | }); 70 | } 71 | 72 | function lowPassFilterListener() { 73 | $('#lpf').click(function () { 74 | $(this).toggleClass("active"); 75 | $(this).blur(); 76 | if ($(this).hasClass("btn-default")) { 77 | $(this).removeClass("btn-default"); 78 | $(this).addClass("btn-warning"); 79 | lowPassFilterNode.active = true; 80 | $("#freq-slider,#quality-slider").slider("option", "disabled", false); 81 | } else { 82 | $(this).addClass("btn-default"); 83 | $(this).removeClass("btn-warning"); 84 | lowPassFilterNode.active = false; 85 | $("#freq-slider,#quality-slider").slider("option", "disabled", true); 86 | } 87 | }); 88 | } 89 | 90 | function reverbListener() { 91 | $("#reverb").click(function () { 92 | $(this).toggleClass("active"); 93 | $(this).blur(); 94 | if ($(this).hasClass("btn-default")) { 95 | $(this).removeClass("btn-default"); 96 | $(this).addClass("btn-warning"); 97 | convolver.active = true; 98 | } else { 99 | $(this).addClass("btn-default"); 100 | $(this).removeClass("btn-warning"); 101 | convolver.active = false; 102 | } 103 | }) 104 | } 105 | 106 | function changeFrequency(event, ui) { 107 | var minValue = 40; 108 | var maxValue = context.sampleRate / 2; 109 | var numberOfOctaves = Math.log(maxValue / minValue) / Math.LN2; 110 | var multiplier = Math.pow(2, numberOfOctaves * (ui.value - 1.0)); 111 | lowPassFilterNode.frequency.value = maxValue * multiplier; 112 | } 113 | 114 | function changeQuality(event, ui) { 115 | //30 is the quality multiplier, for now. 116 | lowPassFilterNode.Q.value = ui.value * 30; 117 | } 118 | 119 | function checkAndTrigerPlayPause() { 120 | var $span = $('#play-pause').children("span"); 121 | if ($span.hasClass('glyphicon-play')) { 122 | $span.removeClass('glyphicon-play'); 123 | $span.addClass('glyphicon-pause'); 124 | isPlaying = 1; 125 | handlePlay(); 126 | } else { 127 | isPlaying = 0; 128 | $span.addClass('glyphicon-play'); 129 | $span.removeClass('glyphicon-pause'); 130 | handleStop(); 131 | } 132 | } 133 | 134 | // TODO: work on this space stuff 135 | // Currently many keys are mapped to the play/pause button, and it avoids using them in the text inputes (space, arrows, delete, ...) 136 | //$(window).keypress(function (e) { 137 | // if (e.charCode === 0 || e.charCode === 32) { 138 | // e.preventDefault(); 139 | // CheckAndTrigerPlayPause(); 140 | // } 141 | //}) 142 | 143 | function checkAndTrigerRecord() { 144 | if (!isRecording) { 145 | console.log("Record is triggered"); 146 | isRecording = 1; 147 | $('#record').css('color', 'red'); 148 | mediaRecorder.start(); 149 | } else { 150 | console.log("Record is untriggered"); 151 | isRecording = 0; 152 | $('#record').css('color', 'white'); 153 | mediaRecorder.stop(); 154 | } 155 | } 156 | 157 | function playPauseListener() { 158 | $('#play-pause').click(function () { 159 | checkAndTrigerPlayPause(); 160 | }); 161 | } 162 | 163 | function RecordListener() { 164 | $('#record').click(function () { 165 | checkAndTrigerRecord(); 166 | }); 167 | } 168 | 169 | function onDataAvailableInRecorderFunc(evt) { 170 | // push each chunk (blobs) in an array 171 | if (evt.data.size > 0) { 172 | chunks.push(evt.data); 173 | var blob = new Blob(chunks, { 174 | 'type': 'audio/ogg; codecs=opus' 175 | }); 176 | var soundSrc = URL.createObjectURL(blob); 177 | var newHtmlEl = '
Download
'; 178 | $(newHtmlEl).appendTo(".exported-audio"); 179 | chunks = []; 180 | } 181 | } 182 | 183 | function TranslateStateInActions(sequencerState) { 184 | var trackNames = sequencerState['trackNames']; 185 | var pads = sequencerState['pads']; 186 | var soundUrls = sequencerState['sounds']; 187 | var waves = sequencerState['waves']; 188 | var sequenceLength = sequencerState['sequenceLength']; 189 | var gains = sequencerState['gains']; 190 | var tempo = sequencerState['tempo']; 191 | 192 | // check if the tracks are already loaded 193 | if (JSON.stringify(sequencerState) != JSON.stringify(currentSequencerState)) { 194 | // Delete all existing tracks 195 | var numLocalTracks = $('.instrument').length; 196 | for (var i = numLocalTracks - 1; i >= 0; i--) { 197 | deleteTrack(i); 198 | } 199 | 200 | currentSequencerState = sequencerState; 201 | 202 | // change tempo 203 | changeTempo(tempo); 204 | 205 | // change seuquence length 206 | changeSequenceLength(sequenceLength); 207 | 208 | // Add tracks and load buffers 209 | for (var j = 0; j < trackNames.length; j++) { 210 | addNewTrack(j, trackNames[j], soundUrls[j], waves[j][0], waves[j][1], gains[j], pads[j]); 211 | } 212 | 213 | // Activate pads 214 | for (var i = 0; i < trackNames.length; i++) { 215 | var trackTabs = pads[i]; 216 | for (var j = 0; j < trackTabs.length; j++) { 217 | toggleSelectedListenerSocket(i, j, trackTabs[j]); 218 | } 219 | } 220 | } 221 | } 222 | 223 | function toggleSelectedListener(padEl) { 224 | padEl.toggleClass("selected"); 225 | var trackId = padEl.parent().parent().index(); 226 | var padClass = padEl.attr('class'); 227 | var padId = padClass.split(' ')[1].split('_')[1]; 228 | var padState = (padEl.hasClass("selected")) ? 1 : 0; 229 | currentSequencerState.pads[trackId][padId] = padState; 230 | return [trackId, padId, padState] 231 | } 232 | 233 | function toggleSelectedListenerSocket(trackId, padId, padState) { 234 | var padEl = $('.instrument').eq(trackId).find('.pad').eq(parseInt(padId)); 235 | var currentState = padEl.hasClass("selected"); 236 | if (currentState) { 237 | if (padState == 0) { 238 | padEl.removeClass("selected"); 239 | } 240 | } else { 241 | if (padState == 1) { 242 | padEl.addClass("selected"); 243 | } 244 | } 245 | currentSequencerState.pads[trackId][padId] = padState; 246 | } 247 | 248 | // SEQUENCER SCHEDULER 249 | function init() { 250 | initializeAudioNodes(); 251 | loadKits(); 252 | loadImpulseResponses(); 253 | } 254 | 255 | function initializeAudioNodes() { 256 | context = new webkitAudioContext(); 257 | recordingDest = context.createMediaStreamDestination(); 258 | mediaRecorder = new MediaRecorder(recordingDest.stream); 259 | 260 | mediaRecorder.ondataavailable = onDataAvailableInRecorderFunc; 261 | 262 | var finalMixNode; 263 | if (context.createDynamicsCompressor && COMPRESSOR_ACTIVATED) { 264 | // Create a dynamics compressor to sweeten the overall mix. 265 | compressor = context.createDynamicsCompressor(); 266 | compressor.connect(context.destination); 267 | finalMixNode = compressor; 268 | } else { 269 | // No compressor available in this implementation. 270 | finalMixNode = context.destination; 271 | } 272 | 273 | // Create master volume. 274 | // for now, the master volume is static, but in the future there will be a slider 275 | masterGainNode = context.createGain(); 276 | masterGainNode.gain.value = 0.7; // reduce overall volume to avoid clipping 277 | masterGainNode.connect(finalMixNode); 278 | 279 | //connect all sounds to masterGainNode to play them 280 | //don't need this for now, no wet dry mix for effects 281 | // // Create effect volume. 282 | // effectLevelNode = context.createGain(); 283 | // effectLevelNode.gain.value = 1.0; // effect level slider controls this 284 | // effectLevelNode.connect(masterGainNode); 285 | 286 | // Create convolver for effect 287 | convolver = context.createConvolver(); 288 | convolver.active = false; 289 | // convolver.connect(effectLevelNode); 290 | 291 | //Create Low Pass Filter 292 | lowPassFilterNode = context.createBiquadFilter(); 293 | //this is for backwards compatibility, the type used to be an integer 294 | lowPassFilterNode.type = (typeof lowPassFilterNode.type === 'string') ? 'lowpass' : 0; // LOWPASS 295 | //default value is max cutoff, or passing all frequencies 296 | lowPassFilterNode.frequency.value = context.sampleRate / 2; 297 | lowPassFilterNode.connect(masterGainNode); 298 | lowPassFilterNode.active = false; 299 | } 300 | 301 | function loadKits() { 302 | //name must be same as path 303 | var kit = new Kit("TR808"); 304 | 305 | //TODO: figure out how to test if a kit is loaded 306 | currentKit = kit; 307 | } 308 | 309 | function loadImpulseResponses() { 310 | reverbImpulseResponse = new ImpulseResponse("sounds/impulse-responses/matrix-reverb2.wav"); 311 | reverbImpulseResponse.load(); 312 | } 313 | 314 | function playNote(buffer, noteTime, startTime, endTime, gainNode, soloMuteNode) { 315 | var voice = context.createBufferSource(); 316 | voice.buffer = buffer; 317 | 318 | var currentLastNode = masterGainNode; 319 | if (lowPassFilterNode.active) { 320 | lowPassFilterNode.connect(currentLastNode); 321 | currentLastNode = lowPassFilterNode; 322 | } 323 | if (convolver.active) { 324 | convolver.buffer = reverbImpulseResponse.buffer; 325 | convolver.connect(currentLastNode); 326 | currentLastNode = convolver; 327 | } 328 | 329 | voice.connect(soloMuteNode); 330 | soloMuteNode.connect(gainNode); 331 | gainNode.connect(currentLastNode); 332 | gainNode.connect(recordingDest); 333 | voice.start(noteTime, startTime, endTime - startTime); 334 | } 335 | 336 | function schedule() { 337 | var currentTime = context.currentTime; 338 | 339 | // The sequence starts at startTime, so normalize currentTime so that it's 0 at the start of the sequence. 340 | currentTime -= startTime; 341 | 342 | while (noteTime < currentTime + 0.200) { 343 | var contextPlayTime = noteTime + startTime; 344 | currentSequencerState.pads.forEach(function (entry, trackId) { 345 | if (entry[rhythmIndex] == 1) { 346 | wave = currentKit.waves[trackId]; 347 | playNote(currentKit.buffers[trackId], 348 | contextPlayTime, wave.startTime, wave.endTime, 349 | currentKit.gainNodes[trackId], currentKit.soloMuteNodes[trackId]); 350 | } 351 | }); 352 | if (noteTime != lastDrawTime) { 353 | lastDrawTime = noteTime; 354 | drawPlayhead(rhythmIndex); 355 | } 356 | if (isPlaying) 357 | advanceNote(); 358 | } 359 | if (isPlaying) 360 | timeoutId = setTimeout(schedule, 50); 361 | } 362 | 363 | function drawPlayhead(xindex) { 364 | var lastIndex = (xindex + currentKit.sequenceLength - 1) % currentKit.sequenceLength; 365 | 366 | //can change this to class selector to select a column 367 | var $newRows = $('.column_' + xindex); 368 | var $oldRows = $('.column_' + lastIndex); 369 | 370 | $newRows.addClass("playing"); 371 | $oldRows.removeClass("playing"); 372 | } 373 | 374 | function advanceNote() { 375 | // Advance time by a 16th note... 376 | tempo = currentSequencerState.tempo; 377 | var secondsPerBeat = 60.0 / tempo; 378 | rhythmIndex++; 379 | if (rhythmIndex == currentKit.sequenceLength) { 380 | rhythmIndex = 0; 381 | } 382 | 383 | //0.25 because each square is a 16th note 384 | noteTime += 0.25 * secondsPerBeat 385 | // if (rhythmIndex % 2) { 386 | // noteTime += (0.25 + kMaxSwing * theBeat.swingFactor) * secondsPerBeat; 387 | // } else { 388 | // noteTime += (0.25 - kMaxSwing * theBeat.swingFactor) * secondsPerBeat; 389 | // } 390 | 391 | } 392 | 393 | function handlePlay(event) { 394 | rhythmIndex = 0; 395 | noteTime = 0.0; 396 | startTime = context.currentTime + 0.005; 397 | schedule(); 398 | } 399 | 400 | function handleStop(event) { 401 | clearTimeout(timeoutId); 402 | $(".pad").removeClass("playing"); 403 | } 404 | 405 | function initializeTempo() { 406 | $("#tempo-input").val(tempo); 407 | } 408 | 409 | function changeTempo(tempo_input) { 410 | tempo = tempo_input; 411 | $("#tempo-input").val(tempo_input); 412 | currentSequencerState.tempo = tempo; 413 | } 414 | 415 | function changeTempoListener() { 416 | $("#increase-tempo").click(function () { 417 | if (tempo < TEMPO_MAX) { 418 | tempo += TEMPO_STEP; 419 | changeTempo(tempo); 420 | sendTempo(tempo); 421 | } 422 | }); 423 | 424 | $("#decrease-tempo").click(function () { 425 | if (tempo > TEMPO_MIN) { 426 | tempo -= TEMPO_STEP; 427 | changeTempo(tempo); 428 | sendTempo(tempo); 429 | } 430 | }); 431 | } 432 | 433 | 434 | // SEQUENCER ACTIONS 435 | function addNewTrackEvent() { 436 | function newTrack() { 437 | var trackName = $('#newTrackName').val(); 438 | var trackId = $('.instrument').length; 439 | // this action needs to be call in the same order in all clients in order to keep same order of tracks 440 | // we first send to server which will emit back the action 441 | // send to server 442 | sendNewTrack(trackName, null); 443 | } 444 | $('#addNewTrack').click(function () { 445 | newTrack(); 446 | }); 447 | $('#add-track-form').submit(function () { 448 | newTrack(); 449 | }); 450 | } 451 | 452 | function addNewTrackDetails() { 453 | $('#trackDetails').fadeIn('slow'); 454 | $('#newTrackName').focus(); 455 | 456 | $('#addNewTrack').on('click', function () { 457 | $('#trackDetails').fadeOut('slow'); 458 | }); 459 | $('#add-track-form').submit(function () { 460 | $('#trackDetails').fadeOut('slow'); 461 | }); 462 | 463 | $('#newTrackName').keyup(function () { 464 | if ($(this).val() != '') { 465 | $('#addNewTrack').removeAttr('disabled'); 466 | } else { 467 | $('#addNewTrack').attr('disabled', 'disabled') 468 | } 469 | }); 470 | } 471 | 472 | function addNewTrack(trackId, trackName, soundUrl = null, startTime = null, endTime = null, gain = -6, pads = null) { 473 | var uniqueTrackId = Date.now(); 474 | 475 | // update sequencer state 476 | currentSequencerState.trackNames[trackId] = trackName; 477 | currentSequencerState.pads[trackId] = pads !== null ? pads : Array(64).fill(0); 478 | currentSequencerState.sounds[trackId] = soundUrl; 479 | currentSequencerState.waves[trackId] = [startTime, endTime]; 480 | currentSequencerState.gains[trackId] = gain; 481 | 482 | // create html 483 | var padEl = '
\n\n
\n'; 484 | 485 | for (var i = 1; i < MAXLENGTH; i++) { 486 | if (i < currentKit.sequenceLength) { 487 | if (i % 16 == 0 && i != 0) { 488 | padEl = padEl + '
'; 489 | } 490 | padEl = padEl + '
\n\n
\n'; 491 | } else { 492 | if (i % 16 == 0 && i != 0) { 493 | padEl = padEl + '
'; 494 | } 495 | padEl = padEl + '\n'; 496 | } 497 | } 498 | 499 | var newTrack = '
' + 508 | padEl + 509 | '
' + 510 | '' + 511 | '' + 512 | '
' + 513 | '
'; 516 | 517 | var prevTrack = $('#newTrack'); 518 | prevTrack.before(newTrack); 519 | 520 | var thisTrack = $('.instrument').eq(trackId); 521 | 522 | // add gainNode 523 | currentKit.gainNodes[trackId] = context.createGain(); 524 | addKnob(trackId, gain); 525 | 526 | // add solo mute gain node 527 | currentKit.soloMuteNodes[trackId] = context.createGain(); 528 | currentKit.mutedTracks[trackId] = 1; 529 | currentKit.soloedTracks[trackId] = 0; 530 | 531 | // load wavesurfer visu 532 | currentKit.waves[trackId] = new Wave(); 533 | var wave = currentKit.waves[trackId]; 534 | var waveContainer = thisTrack.find('.waveform-container')[0]; 535 | wave.init(thisTrack, waveContainer); 536 | addRefreshRegionEvent(trackId); 537 | 538 | // load the edit visu on the first collapse 539 | thisTrack.children('.edit-zone').on('shown.bs.collapse', function () { 540 | if (!wave.loadedAfterCollapse) { 541 | wave.reload(); 542 | } 543 | }); 544 | 545 | // load buffer 546 | if (soundUrl) { 547 | currentKit.loadSample(soundUrl, trackId); 548 | if (startTime !== 'null' && startTime !== false) { 549 | wave.startTime = startTime; 550 | wave.endTime = endTime; 551 | } 552 | } 553 | 554 | // add click events 555 | addPadClickEvent(socket, trackId); 556 | addDeleteTrackClickEvent(trackId); 557 | addRotateTriangleEvent(trackId); 558 | addMuteTrackEvent(trackId); 559 | addSoloTrackEvent(trackId); 560 | } 561 | 562 | 563 | // gain knob 564 | function addKnob(trackId, gain) { 565 | var knob = $('.instrument').eq(trackId).find(".dial"); 566 | knob.knob({ 567 | width: 30, 568 | height: 30, 569 | min: -35, 570 | max: 6, 571 | step: 1, 572 | displayInput: false, 573 | thickness: 0.5, 574 | change: function (v) { 575 | var trackId = $(this.$).parents('.instrument').index(); 576 | currentKit.changeGainNodeValue(trackId, v); 577 | }, 578 | release: function (v) { 579 | var trackId = $(this.$).parents('.instrument').index(); 580 | currentKit.changeGainNodeValue(trackId, v); 581 | // send db gain value to server 582 | sendTrackGain(trackId, v) 583 | } 584 | }); 585 | knob.val(gain.toString()); 586 | knob.trigger('change'); 587 | currentKit.changeGainNodeValue(trackId, gain); 588 | } 589 | 590 | function changeTrackGain(trackId, gain) { 591 | var knob = $('.instrument').eq(trackId).find(".dial"); 592 | knob.val(gain.toString()); 593 | knob.trigger('change'); 594 | } 595 | 596 | 597 | // change sequence length 598 | function changeNumPads(numPads) { 599 | var instrumentTracks = $('.instrument'); 600 | var numPadsNow = currentKit.sequenceLength; 601 | if (parseInt(numPads) > parseInt(numPadsNow)) { 602 | instrumentTracks.each(function (index) { 603 | var lineBreaks = $(this).children('.pad-container').children('br'); 604 | var pads = $(this).children('.pad-container').children('.pad'); 605 | for (var i = numPadsNow; i < numPads; i++) { 606 | if (i % 16 == 0) { 607 | lineBreaks.eq(i / 16 - 1).show(); 608 | } 609 | pads.eq(i).show(); 610 | } 611 | }); 612 | } else if (parseInt(numPads) < parseInt(numPadsNow)) { 613 | instrumentTracks.each(function (index) { 614 | var lineBreaks = $(this).children('.pad-container').children('br'); 615 | var pads = $(this).children('.pad-container').children('.pad'); 616 | for (var i = numPadsNow - 1; i >= numPads; i--) { 617 | if (i % 16 == 0) { 618 | lineBreaks.eq(i / 16 - 1).hide(); 619 | } 620 | pads.eq(i).hide(); 621 | } 622 | }); 623 | } 624 | } 625 | 626 | function changeSequenceLength(sequenceLength) { 627 | changeNumPads(sequenceLength); 628 | currentKit.changeSequenceLength(sequenceLength); 629 | $('#sequence-length').val(sequenceLength); 630 | } 631 | 632 | function addChangeSequenceLengthEvent() { 633 | var changeLength = function (sequenceLength) { 634 | if (Number.isInteger(parseFloat(sequenceLength)) && parseInt(sequenceLength) <= 64 && parseInt(sequenceLength) > 0) { 635 | changeSequenceLength(sequenceLength); 636 | sendSequenceLength(sequenceLength); 637 | } 638 | } 639 | $('#change-sequence-length-form').submit(function () { 640 | var sequenceLength = $('#sequence-length').val(); 641 | changeLength(sequenceLength); 642 | }); 643 | $('#change-sequence-length').click(function () { 644 | var sequenceLength = $('#sequence-length').val(); 645 | changeLength(sequenceLength); 646 | }); 647 | $('#sequence-length').change(function () { 648 | var sequenceLength = $('#sequence-length').val(); 649 | changeLength(sequenceLength); 650 | }); 651 | } 652 | 653 | 654 | // delete track 655 | function addDeleteTrackClickEvent(trackId) { 656 | var deleteButton = $('.instrument').eq(trackId).find(".deleteTrackButton")[0]; 657 | $(deleteButton).click(function () { 658 | var trackId = $(this).parents('.instrument').index(); 659 | // this action needs to be call in the same order in all clients in order to keep same order of tracks 660 | //deleteTrack(trackId); 661 | // send to serveur 662 | sendDeleteTrack(trackId); 663 | }); 664 | } 665 | 666 | function deleteTrack(trackId) { 667 | // delete html 668 | $('.instrument').eq(trackId).remove(); 669 | 670 | // delete buffer 671 | currentKit.buffers.splice(trackId, 1); 672 | 673 | // delete wave 674 | currentKit.waves.splice(trackId, 1); 675 | 676 | // delete gain 677 | currentKit.gainNodes.splice(trackId, 1); 678 | 679 | // delete gain and solo mute lists 680 | currentKit.soloMuteNodes.splice(trackId, 1); 681 | currentKit.mutedTracks.splice(trackId, 1); 682 | currentKit.soloedTracks.splice(trackId, 1); 683 | 684 | // update sequencer state 685 | currentSequencerState.trackNames.splice(trackId, 1); 686 | currentSequencerState.sounds.splice(trackId, 1); 687 | currentSequencerState.pads.splice(trackId, 1); 688 | currentSequencerState.waves.splice(trackId, 1); 689 | currentSequencerState.gains.splice(trackId, 1); 690 | } 691 | 692 | 693 | // Mute solo track 694 | function addMuteTrackEvent(trackId) { 695 | var muteTrackButton = $('.instrument').eq(trackId).find('.mute-track').eq(0); 696 | muteTrackButton.click(function () { 697 | $(this).trigger("blur"); 698 | var trackId = $(this).parents('.instrument').index(); 699 | toggleMuteTrack(trackId); 700 | solveMuteSoloConflicts(); 701 | }); 702 | } 703 | 704 | function addSoloTrackEvent(trackId) { 705 | var soloTrackButton = $('.instrument').eq(trackId).find('.solo-track').eq(0); 706 | soloTrackButton.click(function () { 707 | $(this).trigger("blur"); 708 | var trackId = $(this).parents('.instrument').index(); 709 | toggleSoloTrack(trackId); 710 | solveMuteSoloConflicts(); 711 | }); 712 | } 713 | 714 | function toggleSoloTrack(trackId) { 715 | currentKit.soloedTracks[trackId] = (currentKit.soloedTracks[trackId] == 1) ? 0 : 1; 716 | if (currentKit.soloedTracks[trackId] == 1 && currentKit.mutedTracks[trackId] == 0) { 717 | $('.instrument').eq(trackId).find('.mute-track').eq(0).button('toggle'); 718 | toggleMuteTrack(trackId); 719 | } 720 | } 721 | 722 | function toggleMuteTrack(trackId) { 723 | currentKit.mutedTracks[trackId] = (currentKit.mutedTracks[trackId] == 1) ? 0 : 1; 724 | if (currentKit.mutedTracks[trackId] == 0 && currentKit.soloedTracks[trackId] == 1) { 725 | $('.instrument').eq(trackId).find('.solo-track').eq(0).button('toggle'); 726 | toggleSoloTrack(trackId); 727 | } 728 | } 729 | 730 | function solveMuteSoloConflicts() { 731 | var soloedTracks = currentKit.soloedTracks; 732 | var mutedTracks = currentKit.mutedTracks; 733 | 734 | // Check if somes tracks are muted 735 | var payableTracks = soloedTracks.includes(1) ? soloedTracks : mutedTracks; 736 | 737 | for (var trackId = 0; trackId < payableTracks.length; trackId++) { 738 | if (payableTracks[trackId]) { 739 | // track is not muted 740 | currentKit.soloMuteNodes[trackId].gain.value = 1; 741 | } else { 742 | // track is muted 743 | currentKit.soloMuteNodes[trackId].gain.value = 0; 744 | } 745 | } 746 | } 747 | 748 | // Drag and drop sounds 749 | function allowDrop(ev) { 750 | ev.preventDefault(); 751 | var target = ev.target; 752 | var trackEl = $(target).hasClass('row') ? $(target) : $(target).parents('.row'); 753 | trackEl.addClass("drop-over"); 754 | } 755 | 756 | function exitDrop(ev) { 757 | ev.preventDefault(); 758 | var target = ev.target; 759 | var trackEl = $(target).hasClass('row') ? $(target) : $(target).parents('.row'); 760 | trackEl.removeClass("drop-over"); 761 | } 762 | 763 | function drag(ev) { 764 | currentSoundUrl = ev.target.getAttribute("sound-url"); 765 | ev.dataTransfer.setData("text", ""); 766 | } 767 | 768 | function drop(ev) { 769 | ev.preventDefault(); 770 | var target = ev.target; 771 | var trackEl = $(target).hasClass('row') ? $(target) : $(target).parents('.row'); 772 | var trackId = trackEl.index(); 773 | currentKit.loadSample(currentSoundUrl, trackId); 774 | sendLoadSound(trackId, currentSoundUrl); 775 | trackEl.removeClass("drop-over"); 776 | } 777 | 778 | 779 | // Wave visu 780 | function addRefreshRegionEvent(trackId) { 781 | var refreshButton = $('.instrument').eq(trackId).children(".edit-zone").children(".refreshWaveRegionButton")[0]; 782 | $(refreshButton).click(function () { 783 | var trackId = $(this).parents('.instrument').index(); 784 | currentKit.waves[trackId].restartRegion(); 785 | currentKit.waves[trackId].sendRegion(); 786 | }); 787 | } 788 | 789 | 790 | // enable/disable search button 791 | $('#search-query').keyup(function () { 792 | if ($(this).val() != '') { 793 | $('#search-button').removeAttr('disabled'); 794 | } else { 795 | $('#search-button').attr('disabled', 'disabled') 796 | } 797 | }); 798 | 799 | // rotate triangle dropdown 800 | function addRotateTriangleEvent(trackId) { 801 | $(".instrument-label").eq(trackId).click(function () { 802 | var trackId = $(this).parents('.instrument').index(); 803 | $('.instrument').eq(trackId).find(".glyphicon").toggleClass('rotation'); 804 | }); 805 | } 806 | 807 | 808 | // Presets 809 | function saveCurrentSequencerstatePreset(presetName) { 810 | sendSequencerPreset(JSON.stringify(currentSequencerState), presetName); 811 | } 812 | 813 | function loadSequencerStatePreset(sequencerPresetState) { 814 | TranslateStateInActions(JSON.parse(sequencerPresetState)); 815 | } 816 | 817 | $("#save-preset").click(function () { 818 | var presetName = sequencerPresetNames.length; 819 | saveCurrentSequencerstatePreset(presetName); 820 | }); 821 | 822 | function addSequencerPreset(presetName) { 823 | sequencerPresetNames.push(presetName); 824 | $("#preset-container").append(''); 825 | $("#preset-" + presetName).click(function () { 826 | sendLoadSequencerPreset(sequencerPresetNames.indexOf(presetName)); 827 | }); 828 | } -------------------------------------------------------------------------------- /static/javascripts/chat.js: -------------------------------------------------------------------------------- 1 | // Chat adapted from: https://github.com/socketio/socket.io/tree/master/examples/chat 2 | var chatRoom; 3 | 4 | function setRoomForChat(room_input) { 5 | console.log("[Chat] Room will be", room_input); 6 | chatRoom = room_input; 7 | } 8 | 9 | $(function () { 10 | var FADE_TIME = 150; // ms 11 | var TYPING_TIMER_LENGTH = 400; // ms 12 | var COLORS = [ 13 | '#e21400', '#91580f', '#f8a700', '#f78b00', 14 | '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', 15 | '#3b88eb', '#3824aa', '#a700ff', '#d300e7' 16 | ]; 17 | 18 | // Initialize variables 19 | var $window = $(window); 20 | var $usernameInput = $('.usernameInput'); // Input for username 21 | var $messages = $('.messages'); // Messages area 22 | var $inputMessage = $('.inputMessage'); // Input message input box 23 | 24 | var $loginPage = $('.login.page'); // The login page 25 | var $chatPage = $('.chat.page'); // The chatroom page 26 | 27 | // Prompt for setting a username 28 | var username; 29 | var connected = false; 30 | var typing = false; 31 | var lastTypingTime; 32 | var $currentInput = $usernameInput.focus(); 33 | 34 | 35 | function addParticipantsMessage(data) { 36 | var message = ''; 37 | if (data.numUsers === 1) { 38 | message += "there's 1 participant"; 39 | } else { 40 | message += "there are " + data.numUsers + " participants"; 41 | } 42 | log(message); 43 | } 44 | 45 | //validate username 46 | $('.usernameInput').addClass("red"); 47 | $('.usernameInput').css("border-color", "red"); 48 | redFormInterval = setInterval(function () { 49 | if ($('.usernameInput').hasClass("red")) { 50 | $('.usernameInput').css("border-color", "transparent"); 51 | $('.usernameInput').removeClass("red"); 52 | } else { 53 | $('.usernameInput').css("border-color", "red"); 54 | $('.usernameInput').addClass("red"); 55 | } 56 | }, 800); 57 | $('#change-nickname').click(function () { 58 | setUsername(); 59 | $('#change-nickname').prop("disabled", true); 60 | clearInterval(redFormInterval); 61 | $('.usernameInput').css("border-color", "transparent"); 62 | }); 63 | $('#nickname-form').submit(function () { 64 | setUsername(); 65 | $('#change-nickname').prop("disabled", true); 66 | clearInterval(redFormInterval); 67 | $('.usernameInput').css("border-color", "transparent"); 68 | }); 69 | 70 | 71 | // Sets the client's username 72 | function setUsername() { 73 | username = cleanInput($usernameInput.val().trim()); 74 | // If the username is valid 75 | if (username) { 76 | $loginPage.fadeOut(); 77 | $chatPage.show(); 78 | $loginPage.off('click'); 79 | $currentInput = $inputMessage.focus(); 80 | 81 | // Tell the server your username 82 | socket.emit('add user', username); 83 | } 84 | } 85 | 86 | // Sends a chat message 87 | function sendMessage() { 88 | var message = $inputMessage.val(); 89 | // Prevent markup from being injected into the message 90 | message = cleanInput(message); 91 | // if there is a non-empty message and a socket connection 92 | if (message && connected) { 93 | $inputMessage.val(''); 94 | addChatMessage({ 95 | username: username.concat(': '), 96 | message: message 97 | }); 98 | // tell server to execute 'new message' and send along one parameter 99 | socket.emit('new message', ': '.concat(message)); 100 | console.log('Chat message sent: ' + message) 101 | } 102 | } 103 | 104 | // Log a message 105 | function log(message, options) { 106 | var $el = $('
  • ').addClass('log').text(message); 107 | addMessageElement($el, options); 108 | } 109 | 110 | // Adds the visual chat message to the message list 111 | function addChatMessage(data, options) { 112 | // Don't fade the message in if there is an 'X was typing' 113 | var $typingMessages = getTypingMessages(data); 114 | options = options || {}; 115 | if ($typingMessages.length !== 0) { 116 | options.fade = false; 117 | $typingMessages.remove(); 118 | } 119 | 120 | var $usernameDiv = $(' ') 121 | .text(data.username) 122 | .css('color', getUsernameColor(data.username)); 123 | var $messageBodyDiv = $(' ') 124 | .text(data.message); 125 | 126 | if (!data.typing) { 127 | newEventMessage(); 128 | } 129 | 130 | var typingClass = data.typing ? 'typing' : ''; 131 | var $messageDiv = $('
  • ') 132 | .data('username', data.username) 133 | .addClass(typingClass) 134 | .append($usernameDiv, $messageBodyDiv); 135 | 136 | addMessageElement($messageDiv, options); 137 | $messageDiv.scrollTop = $messageDiv.scrollHeight; 138 | } 139 | 140 | // Adds the visual chat typing message 141 | function addChatTyping(data) { 142 | data.typing = true; 143 | data.message = ' is typing'; 144 | addChatMessage(data); 145 | } 146 | 147 | // Removes the visual chat typing message 148 | function removeChatTyping(data) { 149 | getTypingMessages(data).fadeOut(function () { 150 | $(this).remove(); 151 | }); 152 | } 153 | 154 | // Adds a message element to the messages and scrolls to the bottom 155 | // el - The element to add as a message 156 | // options.fade - If the element should fade-in (default = true) 157 | // options.prepend - If the element should prepend 158 | // all other messages (default = false) 159 | function addMessageElement(el, options) { 160 | var $el = $(el); 161 | 162 | // Setup default options 163 | if (!options) { 164 | options = {}; 165 | } 166 | if (typeof options.fade === 'undefined') { 167 | options.fade = true; 168 | } 169 | if (typeof options.prepend === 'undefined') { 170 | options.prepend = false; 171 | } 172 | 173 | // Apply options 174 | if (options.fade) { 175 | $el.hide().fadeIn(FADE_TIME); 176 | } 177 | if (options.prepend) { 178 | $messages.prepend($el); 179 | } else { 180 | $messages.append($el); 181 | } 182 | $messages[0].scrollTop = $messages[0].scrollHeight; 183 | $('.chatArea').scrollTop($('.chatArea')[0].scrollHeight); 184 | } 185 | 186 | // Prevents input from having injected markup 187 | function cleanInput(input) { 188 | return $('
    ').text(input).html(); 189 | } 190 | 191 | // Updates the typing event 192 | function updateTyping() { 193 | if (connected) { 194 | if (!typing) { 195 | typing = true; 196 | socket.emit('typing'); 197 | } 198 | lastTypingTime = (new Date()).getTime(); 199 | 200 | setTimeout(function () { 201 | var typingTimer = (new Date()).getTime(); 202 | var timeDiff = typingTimer - lastTypingTime; 203 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 204 | socket.emit('stop typing'); 205 | typing = false; 206 | } 207 | }, TYPING_TIMER_LENGTH); 208 | } 209 | } 210 | 211 | // Gets the 'X is typing' messages of a user 212 | function getTypingMessages(data) { 213 | return $('.typing.message').filter(function (i) { 214 | return $(this).data('username') === data.username; 215 | }); 216 | } 217 | 218 | // Gets the color of a username through our hash function 219 | function getUsernameColor(username) { 220 | // Compute hash code 221 | var hash = 7; 222 | for (var i = 0; i < username.length; i++) { 223 | hash = username.charCodeAt(i) + (hash << 5) - hash; 224 | } 225 | // Calculate color 226 | var index = Math.abs(hash % COLORS.length); 227 | return COLORS[index]; 228 | } 229 | 230 | // Keyboard events 231 | 232 | $window.keydown(function (event) { 233 | // Auto-focus the current input when a key is typed 234 | /*if (!(event.ctrlKey || event.metaKey || event.altKey)) { 235 | $currentInput.focus(); 236 | }*/ 237 | // When the client hits ENTER on their keyboard 238 | if (event.which === 13) { 239 | if (username) { 240 | sendMessage(); 241 | socket.emit('stop typing'); 242 | typing = false; 243 | } 244 | } 245 | }); 246 | 247 | $inputMessage.on('input', function () { 248 | updateTyping(); 249 | }); 250 | 251 | // Click events 252 | 253 | // Focus input when clicking anywhere on login page 254 | $loginPage.click(function () { 255 | $currentInput.focus(); 256 | }); 257 | 258 | // Focus input when clicking on the message input's border 259 | $inputMessage.click(function () { 260 | $inputMessage.focus(); 261 | }); 262 | 263 | // Socket events 264 | 265 | // Whenever the server emits 'login', log the login message 266 | socket.on('login', function (data) { 267 | connected = true; 268 | // Display the welcome message 269 | var message = "Welcome to room #" + chatRoom + " chat " + username; 270 | log(message, { 271 | prepend: true 272 | }); 273 | addParticipantsMessage(data); 274 | }); 275 | 276 | // Whenever the server emits 'auto-login', auto log with the username message 277 | socket.on('autoLogin', function (data) { 278 | console.log(data); 279 | username = data.username; 280 | if (username) { 281 | $loginPage.fadeOut(); 282 | $chatPage.show(); 283 | $loginPage.off('click'); 284 | $currentInput.val(username) 285 | $('#change-nickname').prop("disabled", true); 286 | clearInterval(redFormInterval); 287 | $('.usernameInput').css("border-color", "transparent"); 288 | connected = true; 289 | // Display the welcome message 290 | var message = "Welcome to room #" + chatRoom + " chat " + username; 291 | log(message, { 292 | prepend: true 293 | }); 294 | addParticipantsMessage(data); 295 | } 296 | }); 297 | 298 | socket.on('user change name', function (data) { 299 | var message = data.oldName + " changed his nickname for " + data.newName; 300 | log(message) 301 | }); 302 | 303 | // Whenever the server emits 'new message', update the chat body 304 | socket.on('new message', function (data) { 305 | addChatMessage(data); 306 | }); 307 | 308 | // Whenever the server emits 'user joined', log it in the chat body 309 | socket.on('user joined', function (data) { 310 | log(data.username + ' joined'); 311 | newEventMessage(); 312 | addParticipantsMessage(data); 313 | }); 314 | 315 | // Whenever the server emits 'user left', log it in the chat body 316 | socket.on('user left', function (data) { 317 | log(data.username + ' left'); 318 | newEventMessage(); 319 | console.log(data.username + ' left') 320 | addParticipantsMessage(data); 321 | removeChatTyping(data); 322 | }); 323 | 324 | // Whenever the server emits 'typing', show the typing message 325 | socket.on('typing', function (data) { 326 | addChatTyping(data); 327 | }); 328 | 329 | // Whenever the server emits 'stop typing', kill the typing message 330 | socket.on('stop typing', function (data) { 331 | removeChatTyping(data); 332 | }); 333 | 334 | socket.on('disconnect', function () { 335 | log('you have been disconnected'); 336 | }); 337 | 338 | socket.on('reconnect', function () { 339 | log('you have been reconnected'); 340 | if (username) { 341 | socket.emit('add user', username); 342 | } 343 | }); 344 | 345 | socket.on('reconnect_error', function () { 346 | log('attempt to reconnect has failed'); 347 | }); 348 | 349 | 350 | // Notification using web page title 351 | var numChatEvent = 0; 352 | var curentlyfocused = 1; 353 | 354 | var title = document.title; 355 | 356 | function newUpdate() { 357 | updateIntervalId = setInterval(changeTitle, 2000); 358 | } 359 | 360 | // Set up event handler for the window focus event 361 | $('#chat-container').focusin(function () { 362 | curentlyfocused = 1; 363 | numChatEvent = 0; 364 | document.title = title; 365 | }); 366 | 367 | // Set up event handler for the window blur event 368 | $('#chat-container').focusout(function () { 369 | curentlyfocused = 0; 370 | numChatEvent = 0; 371 | }); 372 | 373 | // Getting a new message to notify 374 | function newEventMessage () { 375 | numChatEvent++; 376 | } 377 | 378 | $('#site-body').ready(function () { 379 | newUpdate(); 380 | $('#chat-container').click(function () { 381 | $('.inputMessage').focus(); 382 | }); 383 | }); 384 | 385 | function changeTitle() { 386 | if ((numChatEvent > 0) && (!curentlyfocused)) { 387 | var newTitle = '(' + numChatEvent + ') ' + title; 388 | document.title = newTitle; 389 | } 390 | } 391 | }); 392 | -------------------------------------------------------------------------------- /static/javascripts/freesound.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var freesound = function () { 4 | var authHeader = ''; 5 | var clientId = ''; 6 | var clientSecret = ''; 7 | var host = 'freesound.org'; 8 | 9 | var uris = { 10 | base: 'https://' + host + '/apiv2', 11 | textSearch: '/search/text/', 12 | contentSearch: '/search/content/', 13 | combinedSearch: '/sounds/search/combined/', 14 | sound: '/sounds//', 15 | soundAnalysis: '/sounds//analysis/', 16 | similarSounds: '/sounds//similar/', 17 | comments: '/sounds//comments/', 18 | download: '/sounds//download/', 19 | upload: '/sounds/upload/', 20 | describe: '/sounds//describe/', 21 | pending: '/sounds/pending_uploads/', 22 | bookmark: '/sounds//bookmark/', 23 | rate: '/sounds//rate/', 24 | comment: '/sounds//comment/', 25 | authorize: '/oauth2/authorize/', 26 | logout: '/api-auth/logout/', 27 | logoutAuthorize: '/oauth2/logout_and_authorize/', 28 | me: '/me/', 29 | user: '/users//', 30 | userSounds: '/users//sounds/', 31 | userPacks: '/users//packs/', 32 | userBookmarkCategories: '/users//bookmark_categories/', 33 | userBookmarkCategorySounds: '/users//bookmark_categories//sounds/', 34 | pack: '/packs//', 35 | packSounds: '/packs//sounds/', 36 | packDownload: '/packs//download/' 37 | }; 38 | 39 | var makeUri = function (uri, args) { 40 | for (var a in args) { 41 | uri = uri.replace(/<[\w_]+>/, args[a]); 42 | } 43 | return uris.base + uri; 44 | }; 45 | 46 | var makeRequest = function (uri, success, error, params, wrapper, method, data, content_type) { 47 | if (method === undefined) method = 'GET'; 48 | if (!error) error = function (e) { 49 | console.log(e) 50 | }; 51 | params = params || {}; 52 | params['format'] = 'json'; 53 | var fs = this; 54 | var parse_response = function (response) { 55 | var data = eval("(" + response + ")"); 56 | success(wrapper ? wrapper(data) : data); 57 | }; 58 | var paramStr = ""; 59 | for (var p in params) { 60 | paramStr = paramStr + "&" + p + "=" + params[p]; 61 | } 62 | if (paramStr) { 63 | uri = uri + "?" + paramStr; 64 | } 65 | 66 | if (typeof module !== 'undefined') { // node.js 67 | var http = require("http"); 68 | var options = { 69 | host: host, 70 | path: uri.substring(uri.indexOf("/", 8), uri.length), // first '/' after 'http://' 71 | port: '80', 72 | method: method, 73 | headers: { 74 | 'Authorization': authHeader 75 | } 76 | }; 77 | var req = http.request(options, function (res) { 78 | var result = ''; 79 | res.setEncoding('utf8'); 80 | res.on('data', function (data) { 81 | result += data; 82 | }); 83 | res.on('end', function () { 84 | if ([200, 201, 202].indexOf(res.statusCode) >= 0) 85 | success(wrapper ? wrapper(data) : data); 86 | else 87 | error(data); 88 | }); 89 | }); 90 | req.on('error', error).end(); 91 | } else { // browser 92 | var xhr; 93 | try { 94 | xhr = new XMLHttpRequest(); 95 | } catch (e) { 96 | xhr = new ActiveXObject('Microsoft.XMLHTTP'); 97 | } 98 | 99 | xhr.onreadystatechange = function () { 100 | if (xhr.readyState === 4 && [200, 201, 202].indexOf(xhr.status) >= 0) { 101 | var data = eval("(" + xhr.responseText + ")"); 102 | if (success) success(wrapper ? wrapper(data) : data); 103 | } else if (xhr.readyState === 4 && xhr.status !== 200) { 104 | if (error) error(xhr.statusText); 105 | } 106 | }; 107 | xhr.open(method, uri); 108 | xhr.setRequestHeader('Authorization', authHeader); 109 | if (content_type !== undefined) 110 | xhr.setRequestHeader('Content-Type', content_type); 111 | xhr.send(data); 112 | } 113 | }; 114 | var checkOauth = function () { 115 | if (authHeader.indexOf("Bearer") == -1) 116 | throw ("Oauth authentication required"); 117 | }; 118 | 119 | var makeFD = function (obj, fd) { 120 | if (!fd) 121 | fd = new FormData(); 122 | for (var prop in obj) { 123 | fd.append(prop, obj[prop]) 124 | } 125 | return fd; 126 | }; 127 | 128 | var search = function (options, uri, success, error, wrapper) { 129 | if (options.analysis_file) { 130 | makeRequest(makeUri(uri), success, error, null, wrapper, 'POST', makeFD(options)); 131 | } else { 132 | makeRequest(makeUri(uri), success, error, options, wrapper); 133 | } 134 | }; 135 | 136 | var Collection = function (jsonObject) { 137 | var nextOrPrev = function (which, success, error) { 138 | makeRequest(which, success, error, {}, Collection); 139 | }; 140 | jsonObject.nextPage = function (success, error) { 141 | nextOrPrev(jsonObject.next, success, error); 142 | }; 143 | jsonObject.previousPage = function (success, error) { 144 | nextOrPrev(jsonObject.previous, success, error); 145 | }; 146 | jsonObject.getItem = function (idx) { 147 | return jsonObject.results[idx]; 148 | } 149 | 150 | return jsonObject; 151 | }; 152 | 153 | var SoundCollection = function (jsonObject) { 154 | var collection = Collection(jsonObject); 155 | collection.getSound = function (idx) { 156 | return new SoundObject(collection.results[idx]); 157 | }; 158 | return collection; 159 | }; 160 | 161 | var PackCollection = function (jsonObject) { 162 | var collection = Collection(jsonObject); 163 | collection.getPack = function (idx) { 164 | return new PackObject(collection.results[idx]); 165 | }; 166 | return collection; 167 | }; 168 | 169 | var SoundObject = function (jsonObject) { 170 | jsonObject.getAnalysis = function (filter, success, error, showAll) { 171 | var params = { 172 | all: showAll ? 1 : 0 173 | }; 174 | makeRequest(makeUri(uris.soundAnalysis, [jsonObject.id, filter ? filter : ""]), success, error); 175 | }; 176 | 177 | jsonObject.getSimilar = function (success, error, params) { 178 | makeRequest(makeUri(uris.similarSounds, [jsonObject.id]), success, error, params, SoundCollection); 179 | }; 180 | 181 | jsonObject.getComments = function (success, error) { 182 | makeRequest(makeUri(uris.comments, [jsonObject.id]), success, error, {}, Collection); 183 | }; 184 | 185 | jsonObject.download = function (targetWindow) { // can be window, new, or iframe 186 | checkOauth(); 187 | var uri = makeUri(uris.download, [jsonObject.id]); 188 | targetWindow.location = uri; 189 | }; 190 | 191 | jsonObject.comment = function (commentStr, success, error) { 192 | checkOauth(); 193 | var data = new FormData(); 194 | data.append('comment', comment); 195 | var uri = makeUri(uris.comment, [jsonObject.id]); 196 | makeRequest(uri, success, error, {}, null, 'POST', data); 197 | }; 198 | 199 | jsonObject.rate = function (rating, success, error) { 200 | checkOauth(); 201 | var data = new FormData(); 202 | data.append('rating', rating); 203 | var uri = makeUri(uris.rate, [jsonObject.id]); 204 | makeRequest(uri, success, error, {}, null, 'POST', data); 205 | }; 206 | 207 | jsonObject.bookmark = function (name, category, success, error) { 208 | checkOauth(); 209 | var data = new FormData(); 210 | data.append('name', name); 211 | if (category) 212 | data.append("category", category); 213 | var uri = makeUri(uris.bookmark, [jsonObject.id]); 214 | makeRequest(uri, success, error, {}, null, 'POST', data); 215 | }; 216 | 217 | jsonObject.edit = function (description, success, error) { 218 | checkOauth(); 219 | var data = makeFD(description); 220 | var uri = makeUri(uris.edit, [jsonObject.id]); 221 | makeRequest(uri, success, error, {}, null, 'POST', data); 222 | }; 223 | 224 | return jsonObject; 225 | }; 226 | var UserObject = function (jsonObject) { 227 | jsonObject.sounds = function (success, error, params) { 228 | var uri = makeUri(uris.userSounds, [jsonObject.username]); 229 | makeRequest(uri, success, error, params, SoundCollection); 230 | }; 231 | 232 | jsonObject.packs = function (success, error) { 233 | var uri = makeUri(uris.userPacks, [jsonObject.username]); 234 | makeRequest(uri, success, error, {}, PackCollection); 235 | }; 236 | 237 | jsonObject.bookmarkCategories = function (success, error) { 238 | var uri = makeUri(uris.userBookmarkCategories, [jsonObject.username]); 239 | makeRequest(uri, success, error); 240 | }; 241 | 242 | jsonObject.bookmarkCategorySounds = function (success, error, params) { 243 | var uri = makeUri(uris.userBookmarkCategorySounds, [jsonObject.username]); 244 | makeRequest(uri, success, error, params); 245 | }; 246 | 247 | return jsonObject; 248 | }; 249 | 250 | var PackObject = function (jsonObject) { 251 | jsonObject.sounds = function (success, error) { 252 | var uri = makeUri(uris.packSounds, [jsonObject.id]); 253 | makeRequest(uri, success, error, {}, SoundCollection); 254 | }; 255 | 256 | jsonObject.download = function (targetWindow) { // can be current or new window, or iframe 257 | checkOauth(); 258 | var uri = makeUri(uris.packDownload, [jsonObject.id]); 259 | targetWindow.location = uri; 260 | }; 261 | return jsonObject; 262 | }; 263 | 264 | return { 265 | // authentication 266 | setToken: function (token, type) { 267 | authHeader = (type === 'oauth' ? 'Bearer ' : 'Token ') + token; 268 | }, 269 | setClientSecrets: function (id, secret) { 270 | clientId = id; 271 | clientSecret = secret; 272 | }, 273 | 274 | postAccessCode: function (code, success, error) { 275 | var post_url = uris.base + "/oauth2/access_token/" 276 | var data = new FormData(); 277 | data.append('client_id', clientId); 278 | data.append('client_secret', clientSecret); 279 | data.append('code', code); 280 | data.append('grant_type', 'authorization_code'); 281 | 282 | if (!success) { 283 | success = function (result) { 284 | setToken(result.access_token, 'oauth'); 285 | } 286 | } 287 | makeRequest(post_url, success, error, {}, null, 'POST', data); 288 | }, 289 | textSearch: function (query, options, success, error) { 290 | options = options || {}; 291 | options.query = query ? query : " "; 292 | search(options, uris.textSearch, success, error, SoundCollection); 293 | }, 294 | contentSearch: function (options, success, error) { 295 | if (!(options.target || options.analysis_file)) 296 | throw ("Missing target or analysis_file"); 297 | search(options, uris.contentSearch, success, error, SoundCollection); 298 | }, 299 | combinedSearch: function (options, success, error) { 300 | if (!(options.target || options.analysis_file || options.query)) 301 | throw ("Missing query, target or analysis_file"); 302 | search(options, uris.contentSearch, success, error); 303 | }, 304 | getSound: function (soundId, success, error) { 305 | makeRequest(makeUri(uris.sound, [soundId]), success, error, {}, SoundObject); 306 | }, 307 | 308 | upload: function (audiofile, filename, description, success, error) { 309 | checkOauth(); 310 | var fd = new FormData(); 311 | fd.append('audiofile', audiofile, filename); 312 | if (description) { 313 | fd = makeFD(description, fd); 314 | } 315 | makeRequest(makeUri(uris.upload), success, error, {}, null, 'POST', fd); 316 | }, 317 | describe: function (upload_filename, description, license, tags, success, error) { 318 | checkOauth(); 319 | var fd = makeFD(description); 320 | makeRequest(makeUri(uris.upload), success, error, {}, null, 'POST', fd); 321 | }, 322 | 323 | getPendingSounds: function (success, error) { 324 | checkOauth(); 325 | makeRequest(makeUri(uris.pending), success, error, {}); 326 | }, 327 | 328 | // user resources 329 | me: function (success, error) { 330 | checkOauth(); 331 | makeRequest(makeUri(uris.me), success, error); 332 | }, 333 | 334 | getLoginURL: function () { 335 | if (clientId === undefined) throw "client_id was not set" 336 | var login_url = makeUri(uris.authorize); 337 | login_url += "?client_id=" + clientId + "&response_type=code"; 338 | return login_url; 339 | }, 340 | getLogoutURL: function () { 341 | var logout_url = makeUri(uris.logoutAuthorize); 342 | logout_url += "?client_id=" + clientId + "&response_type=code"; 343 | 344 | return logout_url; 345 | }, 346 | 347 | getUser: function (username, success, error) { 348 | makeRequest(makeUri(uris.user, [username]), success, error, {}, UserObject); 349 | }, 350 | 351 | getPack: function (packId, success, error) { 352 | makeRequest(makeUri(uris.pack, [packId]), success, error, {}, PackObject); 353 | } 354 | } 355 | }; 356 | 357 | // compatible with CommonJS (node), AMD (requireJS) failing back to browser global 358 | // working with node requires web-audio-api module 359 | if (typeof module !== 'undefined') { 360 | module.exports = freesound(); 361 | } else if (typeof define === 'function' && typeof define.amd === 'object') { 362 | define("freesound", [], freesound); 363 | } else { 364 | this.freesound = freesound(); 365 | } 366 | }()); -------------------------------------------------------------------------------- /static/javascripts/impulse-response.js: -------------------------------------------------------------------------------- 1 | function ImpulseResponse(url) { 2 | this.url = url; 3 | this.startedLoading = false; 4 | this.isLoaded = false; 5 | this.buffer = null; 6 | } 7 | 8 | ImpulseResponse.prototype.load = function() { 9 | if (this.startedLoading) { 10 | return; 11 | } 12 | 13 | var request = new XMLHttpRequest(); 14 | request.open("GET", this.url, true); 15 | request.responseType = "arraybuffer"; 16 | this.request = request; 17 | 18 | var asset = this; 19 | 20 | this.startedLoading = true; 21 | request.onload = function() { 22 | context.decodeAudioData( 23 | request.response, 24 | function(buffer) { 25 | asset.buffer = buffer; 26 | asset.isLoaded = true; 27 | }, 28 | function(buffer) { 29 | console.log("Error decoding impulse response!"); 30 | } 31 | ); 32 | } 33 | request.send(); 34 | }; -------------------------------------------------------------------------------- /static/javascripts/jquery.knob.min.js: -------------------------------------------------------------------------------- 1 | (function(e){if(typeof define==="function"&&define.amd){define(["jquery"],e)}else{e(jQuery)}})(function(e){"use strict";var t={},n=Math.max,r=Math.min;t.c={};t.c.d=e(document);t.c.t=function(e){return e.originalEvent.touches.length-1};t.o=function(){var n=this;this.o=null;this.$=null;this.i=null;this.g=null;this.v=null;this.cv=null;this.x=0;this.y=0;this.w=0;this.h=0;this.$c=null;this.c=null;this.t=0;this.isInit=false;this.fgColor=null;this.pColor=null;this.dH=null;this.cH=null;this.eH=null;this.rH=null;this.scale=1;this.relative=false;this.relativeWidth=false;this.relativeHeight=false;this.$div=null;this.run=function(){var t=function(e,t){var r;for(r in t){n.o[r]=t[r]}n._carve().init();n._configure()._draw()};if(this.$.data("kontroled"))return;this.$.data("kontroled",true);this.extend();this.o=e.extend({min:this.$.data("min")!==undefined?this.$.data("min"):0,max:this.$.data("max")!==undefined?this.$.data("max"):100,stopper:true,readOnly:this.$.data("readonly")||this.$.attr("readonly")==="readonly",cursor:this.$.data("cursor")===true&&30||this.$.data("cursor")||0,thickness:this.$.data("thickness")&&Math.max(Math.min(this.$.data("thickness"),1),.01)||.35,lineCap:this.$.data("linecap")||"butt",width:this.$.data("width")||200,height:this.$.data("height")||200,displayInput:this.$.data("displayinput")==null||this.$.data("displayinput"),displayPrevious:this.$.data("displayprevious"),fgColor:this.$.data("fgcolor")||"#87CEEB",inputColor:this.$.data("inputcolor"),font:this.$.data("font")||"Arial",fontWeight:this.$.data("font-weight")||"bold",inline:false,step:this.$.data("step")||1,rotation:this.$.data("rotation"),draw:null,change:null,cancel:null,release:null,format:function(e){return e},parse:function(e){return parseFloat(e)}},this.o);this.o.flip=this.o.rotation==="anticlockwise"||this.o.rotation==="acw";if(!this.o.inputColor){this.o.inputColor=this.o.fgColor}if(this.$.is("fieldset")){this.v={};this.i=this.$.find("input");this.i.each(function(t){var r=e(this);n.i[t]=r;n.v[t]=n.o.parse(r.val());r.bind("change blur",function(){var e={};e[t]=r.val();n.val(n._validate(e))})});this.$.find("legend").remove()}else{this.i=this.$;this.v=this.o.parse(this.$.val());this.v===""&&(this.v=this.o.min);this.$.bind("change blur",function(){n.val(n._validate(n.o.parse(n.$.val())))})}!this.o.displayInput&&this.$.hide();this.$c=e(document.createElement("canvas")).attr({width:this.o.width,height:this.o.height});this.$div=e('
    ');this.$.wrap(this.$div).before(this.$c);this.$div=this.$.parent();if(typeof G_vmlCanvasManager!=="undefined"){G_vmlCanvasManager.initElement(this.$c[0])}this.c=this.$c[0].getContext?this.$c[0].getContext("2d"):null;if(!this.c){throw{name:"CanvasNotSupportedException",message:"Canvas not supported. Please use excanvas on IE8.0.",toString:function(){return this.name+": "+this.message}}}this.scale=(window.devicePixelRatio||1)/(this.c.webkitBackingStorePixelRatio||this.c.mozBackingStorePixelRatio||this.c.msBackingStorePixelRatio||this.c.oBackingStorePixelRatio||this.c.backingStorePixelRatio||1);this.relativeWidth=this.o.width%1!==0&&this.o.width.indexOf("%");this.relativeHeight=this.o.height%1!==0&&this.o.height.indexOf("%");this.relative=this.relativeWidth||this.relativeHeight;this._carve();if(this.v instanceof Object){this.cv={};this.copy(this.v,this.cv)}else{this.cv=this.v}this.$.bind("configure",t).parent().bind("configure",t);this._listen()._configure()._xy().init();this.isInit=true;this.$.val(this.o.format(this.v));this._draw();return this};this._carve=function(){if(this.relative){var e=this.relativeWidth?this.$div.parent().width()*parseInt(this.o.width)/100:this.$div.parent().width(),t=this.relativeHeight?this.$div.parent().height()*parseInt(this.o.height)/100:this.$div.parent().height();this.w=this.h=Math.min(e,t)}else{this.w=this.o.width;this.h=this.o.height}this.$div.css({width:this.w+"px",height:this.h+"px"});this.$c.attr({width:this.w,height:this.h});if(this.scale!==1){this.$c[0].width=this.$c[0].width*this.scale;this.$c[0].height=this.$c[0].height*this.scale;this.$c.width(this.w);this.$c.height(this.h)}return this};this._draw=function(){var e=true;n.g=n.c;n.clear();n.dH&&(e=n.dH());e!==false&&n.draw()};this._touch=function(e){var r=function(e){var t=n.xy2val(e.originalEvent.touches[n.t].pageX,e.originalEvent.touches[n.t].pageY);if(t==n.cv)return;if(n.cH&&n.cH(t)===false)return;n.change(n._validate(t));n._draw()};this.t=t.c.t(e);r(e);t.c.d.bind("touchmove.k",r).bind("touchend.k",function(){t.c.d.unbind("touchmove.k touchend.k");n.val(n.cv)});return this};this._mouse=function(e){var r=function(e){var t=n.xy2val(e.pageX,e.pageY);if(t==n.cv)return;if(n.cH&&n.cH(t)===false)return;n.change(n._validate(t));n._draw()};r(e);t.c.d.bind("mousemove.k",r).bind("keyup.k",function(e){if(e.keyCode===27){t.c.d.unbind("mouseup.k mousemove.k keyup.k");if(n.eH&&n.eH()===false)return;n.cancel()}}).bind("mouseup.k",function(e){t.c.d.unbind("mousemove.k mouseup.k keyup.k");n.val(n.cv)});return this};this._xy=function(){var e=this.$c.offset();this.x=e.left;this.y=e.top;return this};this._listen=function(){if(!this.o.readOnly){this.$c.bind("mousedown",function(e){e.preventDefault();n._xy()._mouse(e)}).bind("touchstart",function(e){e.preventDefault();n._xy()._touch(e)});this.listen()}else{this.$.attr("readonly","readonly")}if(this.relative){e(window).resize(function(){n._carve().init();n._draw()})}return this};this._configure=function(){if(this.o.draw)this.dH=this.o.draw;if(this.o.change)this.cH=this.o.change;if(this.o.cancel)this.eH=this.o.cancel;if(this.o.release)this.rH=this.o.release;if(this.o.displayPrevious){this.pColor=this.h2rgba(this.o.fgColor,"0.4");this.fgColor=this.h2rgba(this.o.fgColor,"0.6")}else{this.fgColor=this.o.fgColor}return this};this._clear=function(){this.$c[0].width=this.$c[0].width};this._validate=function(e){var t=~~((e<0?-.5:.5)+e/this.o.step)*this.o.step;return Math.round(t*100)/100};this.listen=function(){};this.extend=function(){};this.init=function(){};this.change=function(e){};this.val=function(e){};this.xy2val=function(e,t){};this.draw=function(){};this.clear=function(){this._clear()};this.h2rgba=function(e,t){var n;e=e.substring(1,7);n=[parseInt(e.substring(0,2),16),parseInt(e.substring(2,4),16),parseInt(e.substring(4,6),16)];return"rgba("+n[0]+","+n[1]+","+n[2]+","+t+")"};this.copy=function(e,t){for(var n in e){t[n]=e[n]}}};t.Dial=function(){t.o.call(this);this.startAngle=null;this.xy=null;this.radius=null;this.lineWidth=null;this.cursorExt=null;this.w2=null;this.PI2=2*Math.PI;this.extend=function(){this.o=e.extend({bgColor:this.$.data("bgcolor")||"#EEEEEE",angleOffset:this.$.data("angleoffset")||0,angleArc:this.$.data("anglearc")||360,inline:true},this.o)};this.val=function(e,t){if(null!=e){e=this.o.parse(e);if(t!==false&&e!=this.v&&this.rH&&this.rH(e)===false){return}this.cv=this.o.stopper?n(r(e,this.o.max),this.o.min):e;this.v=this.cv;this.$.val(this.o.format(this.v));this._draw()}else{return this.v}};this.xy2val=function(e,t){var i,s;i=Math.atan2(e-(this.x+this.w2),-(t-this.y-this.w2))-this.angleOffset;if(this.o.flip){i=this.angleArc-i-this.PI2}if(this.angleArc!=this.PI2&&i<0&&i>-.5){i=0}else if(i<0){i+=this.PI2}s=i*(this.o.max-this.o.min)/this.angleArc+this.o.min;this.o.stopper&&(s=n(r(s,this.o.max),this.o.min));return s};this.listen=function(){var t=this,i,s,o=function(e){e.preventDefault();var o=e.originalEvent,u=o.detail||o.wheelDeltaX,a=o.detail||o.wheelDeltaY,f=t._validate(t.o.parse(t.$.val()))+(u>0||a>0?t.o.step:u<0||a<0?-t.o.step:0);f=n(r(f,t.o.max),t.o.min);t.val(f,false);if(t.rH){clearTimeout(i);i=setTimeout(function(){t.rH(f);i=null},100);if(!s){s=setTimeout(function(){if(i)t.rH(f);s=null},200)}}},u,a,f=1,l={37:-t.o.step,38:t.o.step,39:t.o.step,40:-t.o.step};this.$.bind("keydown",function(i){var s=i.keyCode;if(s>=96&&s<=105){s=i.keyCode=s-48}u=parseInt(String.fromCharCode(s));if(isNaN(u)){s!==13&&s!==8&&s!==9&&s!==189&&(s!==190||t.$.val().match(/\./))&&i.preventDefault();if(e.inArray(s,[37,38,39,40])>-1){i.preventDefault();var o=t.o.parse(t.$.val())+l[s]*f;t.o.stopper&&(o=n(r(o,t.o.max),t.o.min));t.change(t._validate(o));t._draw();a=window.setTimeout(function(){f*=2},30)}}}).bind("keyup",function(e){if(isNaN(u)){if(a){window.clearTimeout(a);a=null;f=1;t.val(t.$.val())}}else{t.$.val()>t.o.max&&t.$.val(t.o.max)||t.$.val()this.o.max){this.v=this.o.min}this.$.val(this.v);this.w2=this.w/2;this.cursorExt=this.o.cursor/100;this.xy=this.w2*this.scale;this.lineWidth=this.xy*this.o.thickness;this.lineCap=this.o.lineCap;this.radius=this.xy-this.lineWidth/2;this.o.angleOffset&&(this.o.angleOffset=isNaN(this.o.angleOffset)?0:this.o.angleOffset);this.o.angleArc&&(this.o.angleArc=isNaN(this.o.angleArc)?this.PI2:this.o.angleArc);this.angleOffset=this.o.angleOffset*Math.PI/180;this.angleArc=this.o.angleArc*Math.PI/180;this.startAngle=1.5*Math.PI+this.angleOffset;this.endAngle=1.5*Math.PI+this.angleOffset+this.angleArc;var e=n(String(Math.abs(this.o.max)).length,String(Math.abs(this.o.min)).length,2)+2;this.o.displayInput&&this.i.css({width:(this.w/2+4>>0)+"px",height:(this.w/3>>0)+"px",position:"absolute","vertical-align":"middle","margin-top":(this.w/3>>0)+"px","margin-left":"-"+(this.w*3/4+2>>0)+"px",border:0,background:"none",font:this.o.fontWeight+" "+(this.w/e>>0)+"px "+this.o.font,"text-align":"center",color:this.o.inputColor||this.o.fgColor,padding:"0px","-webkit-appearance":"none"})||this.i.css({width:"0px",visibility:"hidden"})};this.change=function(e){this.cv=e;this.$.val(this.o.format(e))};this.angle=function(e){return(e-this.o.min)*this.angleArc/(this.o.max-this.o.min)};this.arc=function(e){var t,n;e=this.angle(e);if(this.o.flip){t=this.endAngle+1e-5;n=t-e-1e-5}else{t=this.startAngle-1e-5;n=t+e+1e-5}this.o.cursor&&(t=n-this.cursorExt)&&(n=n+this.cursorExt);return{s:t,e:n,d:this.o.flip&&!this.o.cursor}};this.draw=function(){var e=this.g,t=this.arc(this.cv),n,r=1;e.lineWidth=this.lineWidth;e.lineCap=this.lineCap;if(this.o.bgColor!=="none"){e.beginPath();e.strokeStyle=this.o.bgColor;e.arc(this.xy,this.xy,this.radius,this.endAngle-1e-5,this.startAngle+1e-5,true);e.stroke()}if(this.o.displayPrevious){n=this.arc(this.v);e.beginPath();e.strokeStyle=this.pColor;e.arc(this.xy,this.xy,this.radius,n.s,n.e,n.d);e.stroke();r=this.cv==this.v}e.beginPath();e.strokeStyle=r?this.o.fgColor:this.fgColor;e.arc(this.xy,this.xy,this.radius,t.s,t.e,t.d);e.stroke()};this.cancel=function(){this.val(this.v)}};e.fn.dial=e.fn.knob=function(n){return this.each(function(){var r=new t.Dial;r.o=n;r.$=e(this);r.run()}).parent()}}) -------------------------------------------------------------------------------- /static/javascripts/kit.js: -------------------------------------------------------------------------------- 1 | var NUM_INSTRUMENTS = 2; 2 | 3 | function Kit(name) { 4 | this.SAMPLE_BASE_PATH = "assets/sounds/drum-samples/"; 5 | this.name = name; 6 | this.sequenceLength = 16; 7 | this.buffers = []; 8 | this.waves = []; 9 | this.gainNodes = []; 10 | this.soloMuteNodes = []; // gain nodes for soloing and muting tracks 11 | this.mutedTracks = []; // List of Unmuted tracks: If 1, Unmuted, if 0 Muted 12 | this.soloedTracks = []; // List of Soloed tracks: If 1, Soloed, if 0 not Soloed 13 | 14 | this.startedLoading = false; 15 | this.isLoaded = false; 16 | this.instrumentLoadCount = 0; 17 | } 18 | 19 | Kit.prototype.pathName = function() { 20 | return this.SAMPLE_BASE_PATH + this.name + "/"; 21 | }; 22 | 23 | Kit.prototype.changeSequenceLength = function(sequenceLength) { 24 | this.sequenceLength = parseInt(sequenceLength); 25 | currentSequencerState.sequenceLength = sequenceLength; 26 | }; 27 | 28 | Kit.prototype.changeGainNodeValue = function(trackId, value) { 29 | this.gainNodes[trackId].gain.value = linear2db(value); 30 | currentSequencerState.gains[trackId] = value; 31 | }; 32 | 33 | Kit.prototype.loadSample = function(url, trackId) { 34 | // update sequencer state 35 | currentSequencerState.sounds[trackId] = url; 36 | 37 | // load sound in buffer 38 | var request = new XMLHttpRequest(); 39 | request.open("GET", url, true); 40 | request.responseType = "arraybuffer"; 41 | var kit = this; 42 | 43 | // load wavesurfer visu 44 | kit.waves[trackId].clear(); 45 | kit.waves[trackId].load(url); 46 | 47 | request.onload = function () { 48 | context.decodeAudioData( 49 | request.response, 50 | function(buffer) { 51 | kit.buffers[trackId] = buffer; 52 | kit.instrumentLoadCount++; 53 | }, 54 | function(buffer) { 55 | console.log("Error decoding drum samples for track " + trackId); 56 | } 57 | ); 58 | } 59 | request.send(); 60 | }; 61 | 62 | function linear2db(x) { 63 | return Math.pow(10, (x / 20)); 64 | } 65 | -------------------------------------------------------------------------------- /static/javascripts/search.js: -------------------------------------------------------------------------------- 1 | // FREESOUND SEARCH 2 | function initSearch() { 3 | var search = new Search(); 4 | search.setToken(); 5 | search.addButtonEvents(); 6 | return search; 7 | } 8 | 9 | function Search() { 10 | var query = null; 11 | var page = null; 12 | var numPages = null; 13 | var numSounds = null; 14 | var sliderValue = null; 15 | } 16 | 17 | Search.prototype.setToken = function () { 18 | $.get(base_path + '/get_freesound_token') 19 | .done(function(data) { 20 | freesound.setToken(data); 21 | }); 22 | }; 23 | 24 | Search.prototype.freesoundIframe = function (soundId) { 25 | return ''; 26 | }; 27 | 28 | Search.prototype.searchFreesound = function (query, page, filter) { 29 | var self = this; 30 | 31 | self.query = query; 32 | self.page = page; 33 | self.filter = filter; 34 | var sort = "rating_desc"; 35 | freesound.textSearch(query, { 36 | page: page, 37 | filter: filter, 38 | sort: sort, 39 | fields: 'id,name,url,previews', 40 | }, 41 | function (sounds) { 42 | var msg = "" 43 | self.numSounds = sounds.count; 44 | self.numPages = Math.ceil(self.numSounds / 15); 45 | var numSoundCurrentPage = sounds.results.length; 46 | for (i = 0; i < numSoundCurrentPage; i++) { 47 | var snd = sounds.getSound(i); 48 | msg += "
    " + self.freesoundIframe(snd.id) + "
    Drag
    "; 49 | } 50 | msg += "" 51 | document.getElementById("search-result-container").innerHTML = msg; 52 | $('#page').html(self.page + '/' + self.numPages); 53 | $('#next').removeAttr('disabled'); 54 | if (self.page >= self.numPages) { 55 | $('#next').attr('disabled', 'disabled'); 56 | } else { 57 | $('#next').removeAttr('disabled'); 58 | } 59 | if (self.page === 1) { 60 | $('#previous').attr('disabled', 'disabled'); 61 | } else { 62 | $('#previous').removeAttr('disabled'); 63 | } 64 | document.getElementById('error').innerHTML = ""; 65 | }, 66 | function () { 67 | document.getElementById('error').innerHTML = "Error while searching..."; 68 | } 69 | ); 70 | }; 71 | 72 | Search.prototype.addButtonEvents = function () { 73 | var self = this; 74 | $('#search-button').click(function () { 75 | self.searchEvent(); 76 | }); 77 | 78 | $('#search-form').submit(function () { 79 | self.searchEvent(); 80 | }); 81 | 82 | $('#previous').click(function () { 83 | self.page -= 1; 84 | self.searchFreesound(self.query, self.page, self.filter); 85 | }); 86 | 87 | $('#next').click(function () { 88 | self.page += 1; 89 | self.searchFreesound(self.query, self.page, self.filter); 90 | }); 91 | }; 92 | 93 | Search.prototype.searchEvent = function () { 94 | this.query = $('#search-query').val(); 95 | this.sliderValue = $('#sampleDuration').val(); 96 | var duration = "duration:[" + this.sliderValue.split(',')[0] + ".0 TO " + this.sliderValue.split(',')[1] + ".0]" 97 | this.searchFreesound(this.query, 1, duration); 98 | }; -------------------------------------------------------------------------------- /static/javascripts/socket.io.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var n,r=i(t),s=r.source,u=r.id,h=r.path,f=p[u]&&h in p[u].nsps,l=e.forceNew||e["force new connection"]||!1===e.multiplex||f;return l?(c("ignoring socket cache for %s",s),n=a(s,e)):(p[u]||(c("new io instance for %s",s),p[u]=a(s,e)),n=p[u]),r.query&&!e.query&&(e.query=r.query),n.socket(r.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=n(1),s=n(7),a=n(13),c=n(3)("socket.io-client");t.exports=e=r;var p=e.managers={};e.protocol=s.protocol,e.connect=r,e.Manager=n(13),e.Socket=n(37)},function(t,e,n){(function(e){"use strict";function r(t,n){var r=t;n=n||e.location,null==t&&(t=n.protocol+"//"+n.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?n.protocol+t:n.host+t),/^(https?|wss?):\/\//.test(t)||(i("protocol-less url %s",t),t="undefined"!=typeof n?n.protocol+"//"+t:"https://"+t),i("parse %s",t),r=o(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var s=r.host.indexOf(":")!==-1,a=s?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+a+":"+r.port,r.href=r.protocol+"://"+a+(n&&n.port===r.port?"":":"+r.port),r}var o=n(2),i=n(3)("socket.io-client:url");t.exports=r}).call(e,function(){return this}())},function(t,e){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,r=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=n.exec(t||""),a={},c=14;c--;)a[r[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e,n){(function(r){function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var o=0,i=0;t[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,r)}}function s(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function c(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function p(){try{return window.localStorage}catch(t){}}e=t.exports=n(5),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:p(),e.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(c())}).call(e,n(4))},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(u===setTimeout)return setTimeout(t,0);if((u===n||!u)&&setTimeout)return u=setTimeout,setTimeout(t,0);try{return u(t,0)}catch(e){try{return u.call(null,t,0)}catch(e){return u.call(this,t,0)}}}function i(t){if(h===clearTimeout)return clearTimeout(t);if((h===r||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(t);try{return h(t)}catch(e){try{return h.call(null,t)}catch(e){return h.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):m=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++m1)for(var n=1;n100)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*u;case"days":case"day":case"d":return n*p;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){return t>=p?Math.round(t/p)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,p,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,n){if(!(t0)return n(t);if("number"===i&&isNaN(t)===!1)return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(){}function o(t){var n=""+t.type;return e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(n+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(n+=t.nsp+","),null!=t.id&&(n+=t.id),null!=t.data&&(n+=JSON.stringify(t.data)),h("encoded %j as %s",t,n),n}function i(t,e){function n(t){var n=d.deconstructPacket(t),r=o(n.packet),i=n.buffers;i.unshift(r),e(i)}d.removeBlobs(t,n)}function s(){this.reconstructor=null}function a(t){var n=0,r={type:Number(t.charAt(0))};if(null==e.types[r.type])return u();if(e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type){for(var o="";"-"!==t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!==t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"===t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","===i)break;if(r.nsp+=i,n===t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n===t.length)break}r.id=Number(r.id)}return t.charAt(++n)&&(r=c(r,t.substr(n))),h("decoded %s as %j",t,r),r}function c(t,e){try{t.data=JSON.parse(e)}catch(n){return u()}return t}function p(t){this.reconPack=t,this.buffers=[]}function u(){return{type:e.ERROR,data:"parser error"}}var h=n(3)("socket.io-parser"),f=n(8),l=n(9),d=n(11),y=n(12);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=r,e.Decoder=s,r.prototype.encode=function(t,n){if(t.type!==e.EVENT&&t.type!==e.ACK||!l(t.data)||(t.type=t.type===e.EVENT?e.BINARY_EVENT:e.BINARY_ACK),h("encoding packet %j",t),e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)i(t,n);else{var r=o(t);n([r])}},f(s.prototype),s.prototype.add=function(t){var n;if("string"==typeof t)n=a(t),e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type?(this.reconstructor=new p(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!y(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");n=this.reconstructor.takeBinaryData(t),n&&(this.reconstructor=null,this.emit("decoded",n))}},s.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},p.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},p.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,n){function r(t){if(t)return o(t)}function o(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var r,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},r.prototype.cleanup=function(){h("cleanup");for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)h("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();h("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(h("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(h("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(h("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},r.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,n){t.exports=n(15),t.exports.parser=n(22)},function(t,e,n){(function(e){function r(t,n){if(!(this instanceof r))return new r(t,n);n=n||{},t&&"object"==typeof t&&(n=t,t=null),t?(t=u(t),n.hostname=t.host,n.secure="https"===t.protocol||"wss"===t.protocol,n.port=t.port,t.query&&(n.query=t.query)):n.host&&(n.hostname=u(n.host).host),this.secure=null!=n.secure?n.secure:e.location&&"https:"===location.protocol,n.hostname&&!n.port&&(n.port=this.secure?"443":"80"),this.agent=n.agent||!1,this.hostname=n.hostname||(e.location?location.hostname:"localhost"),this.port=n.port||(e.location&&location.port?location.port:this.secure?443:80),this.query=n.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==n.upgrade,this.path=(n.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!n.forceJSONP,this.jsonp=!1!==n.jsonp,this.forceBase64=!!n.forceBase64,this.enablesXDR=!!n.enablesXDR,this.timestampParam=n.timestampParam||"t",this.timestampRequests=n.timestampRequests,this.transports=n.transports||["polling","websocket"],this.transportOptions=n.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=n.policyPort||843,this.rememberUpgrade=n.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=n.onlyBinaryUpgrades,this.perMessageDeflate=!1!==n.perMessageDeflate&&(n.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=n.pfx||null,this.key=n.key||null,this.passphrase=n.passphrase||null,this.cert=n.cert||null,this.ca=n.ca||null,this.ciphers=n.ciphers||null,this.rejectUnauthorized=void 0===n.rejectUnauthorized||n.rejectUnauthorized,this.forceNode=!!n.forceNode;var o="object"==typeof e&&e;o.global===o&&(n.extraHeaders&&Object.keys(n.extraHeaders).length>0&&(this.extraHeaders=n.extraHeaders),n.localAddress&&(this.localAddress=n.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=n(16),s=n(8),a=n(3)("engine.io-client:socket"),c=n(36),p=n(22),u=n(2),h=n(30);t.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=p.protocol,r.Socket=r,r.Transport=n(21),r.transports=n(16),r.parser=n(22),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=p.protocol,e.transport=t;var n=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var r=new i[t]({query:e,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0});return r},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;h=h||e}h||(a('probe transport "%s" opened',t),u.send([{type:"ping",data:"probe"}]),u.once("packet",function(e){if(!h)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",u),!u)return;r.priorWebsocketSuccess="websocket"===u.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){h||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),p(),f.setTransport(u),u.send([{type:"upgrade"}]),f.emit("upgrade",u),u=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=u.name,f.emit("upgradeError",n)}}))}function n(){h||(h=!0,p(),u.close(),u=null)}function o(e){var r=new Error("probe error: "+e);r.transport=u.name,n(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){u&&t.name!==u.name&&(a('"%s" works - aborting "%s"',t.name,u.name),n())}function p(){u.removeListener("open",e),u.removeListener("error",o),u.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var u=this.createTransport(t,{probe:1}),h=!1,f=this;r.priorWebsocketSuccess=!1,u.once("open",e),u.once("error",o),u.once("close",i),this.once("close",s),this.once("upgrading",c),u.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:w}var i=new Uint8Array(t),o=i[0],s=f(t,1);return k&&"blob"===n&&(s=new k([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var n=b[t.charAt(0)];if(!p)return{type:n,data:{base64:!0,data:t.substr(1)}};var r=p.decode(t.substr(1));return"blob"===e&&k&&(r=new k([r])),{type:n,data:r}},e.encodePayload=function(t,n,r){function o(t){return t.length+":"+t}function i(t,r){e.encodePacket(t,!!s&&n,!1,function(t){r(null,o(t))})}"function"==typeof n&&(r=n,n=null);var s=h(t);return n&&s?k&&!g?e.encodePayloadAsBlob(t,r):e.encodePayloadAsArrayBuffer(t,r):t.length?void c(t,i,function(t,e){return r(e.join(""))}):r("0:")},e.decodePayload=function(t,n,r){if("string"!=typeof t)return e.decodePayloadAsBinary(t,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===t)return r(w,0,1);for(var i,s,a="",c=0,p=t.length;c0;){for(var s=new Uint8Array(o),a=0===s[0],c="",p=1;255!==s[p];p++){if(c.length>310)return r(w,0,1);c+=s[p]}o=f(o,2+c.length),c=parseInt(c);var u=f(o,0,c);if(a)try{u=String.fromCharCode.apply(null,new Uint8Array(u))}catch(h){var l=new Uint8Array(u);u="";for(var p=0;pr&&(n=r),e>=r||e>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(n-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=w(e>>>10&1023|55296),e=56320|1023&e),o+=w(e);return o}function c(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function p(t,e){return w(t>>e&63|128)}function u(t,e){if(0==(4294967168&t))return w(t);var n="";return 0==(4294965248&t)?n=w(t>>6&31|192):0==(4294901760&t)?(c(t,e)||(t=65533),n=w(t>>12&15|224),n+=p(t,6)):0==(4292870144&t)&&(n=w(t>>18&7|240),n+=p(t,12),n+=p(t,6)),n+=w(63&t|128)}function h(t,e){e=e||{};for(var n,r=!1!==e.strict,o=s(t),i=o.length,a=-1,c="";++a=v)throw Error("Invalid byte index");var t=255&g[b];if(b++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function l(t){var e,n,r,o,i;if(b>v)throw Error("Invalid byte index");if(b==v)return!1;if(e=255&g[b],b++,0==(128&e))return e;if(192==(224&e)){if(n=f(),i=(31&e)<<6|n,i>=128)return i;throw Error("Invalid continuation byte")}if(224==(240&e)){if(n=f(),r=f(),i=(15&e)<<12|n<<6|r,i>=2048)return c(i,t)?i:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(n=f(),r=f(),o=f(),i=(7&e)<<18|n<<12|r<<6|o,i>=65536&&i<=1114111))return i;throw Error("Invalid UTF-8 detected")}function d(t,e){e=e||{};var n=!1!==e.strict;g=s(t),v=g.length,b=0;for(var r,o=[];(r=l(n))!==!1;)o.push(r);return a(o)}var y="object"==typeof e&&e,m=("object"==typeof t&&t&&t.exports==y&&t,"object"==typeof o&&o);m.global!==m&&m.window!==m||(i=m);var g,v,b,w=String.fromCharCode,k={version:"2.1.2",encode:h,decode:d};r=function(){return k}.call(e,n,e,t),!(void 0!==r&&(t.exports=r))}(this)}).call(e,n(27)(t),function(){return this}())},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r>2],i+=t[(3&r[n])<<4|r[n+1]>>4],i+=t[(15&r[n+1])<<2|r[n+2]>>6],i+=t[63&r[n+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,r,o,i,s,a=.75*t.length,c=t.length,p=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var u=new ArrayBuffer(a),h=new Uint8Array(u);for(e=0;e>4,h[p++]=(15&o)<<4|i>>2,h[p++]=(3&i)<<6|63&s;return u}}()},function(t,e){(function(e){function n(t){for(var e=0;e0);return e}function r(t){var e=0;for(u=0;u';i=document.createElement(e)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),u=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=u,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),r(),t=t.replace(p,"\\\n"),this.area.value=t.replace(c,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&n()}:this.iframe.onload=n}}).call(e,function(){return this}())},function(t,e,n){(function(e){function r(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=h&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(l=o),i.call(this,t)}var o,i=n(21),s=n(22),a=n(30),c=n(31),p=n(32),u=n(3)("engine.io-client:websocket"),h=e.WebSocket||e.MozWebSocket;if("undefined"==typeof window)try{o=n(35)}catch(f){}var l=h;l||"undefined"!=typeof window||(l=o),t.exports=r,c(r,i),r.prototype.name="websocket",r.prototype.supportsBinary=!0,r.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket?e?new l(t,e):new l(t):new l(t,e,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},r.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},r.prototype.write=function(t){function n(){r.emit("flush"),setTimeout(function(){r.writable=!0,r.emit("drain")},0)}var r=this;this.writable=!1;for(var o=t.length,i=0,a=o;i0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}}])}); 3 | //# sourceMappingURL=socket.io.js.map -------------------------------------------------------------------------------- /static/javascripts/wave.js: -------------------------------------------------------------------------------- 1 | function Wave() { 2 | this.wavesurfer = null; 3 | this.region = null; 4 | this.startTime = null; 5 | this.endTime = null; 6 | this.duration = null; 7 | this.trackEl = null; 8 | this.soundUrl = null; 9 | this.loadedAfterCollapse = false; 10 | this.container = null; 11 | } 12 | 13 | Wave.prototype.init = function(trackEl, container) { 14 | this.wavesurfer = WaveSurfer.create({ 15 | cursorWidth: 0, 16 | container: container, 17 | waveColor: 'black', 18 | progressColor: 'black', 19 | height: 50 20 | }); 21 | this.trackEl = trackEl; 22 | this.container = container; 23 | }; 24 | 25 | Wave.prototype.load = function(soundUrl) { 26 | var wavesurfer = this.wavesurfer; 27 | var wave = this; 28 | wave.soundUrl = soundUrl; 29 | wavesurfer.load(soundUrl); 30 | wavesurfer.on('ready', function() { 31 | if (wave.region) { 32 | wave.region.remove(); 33 | } 34 | var duration = wavesurfer.getDuration(); 35 | wave.duration = duration; 36 | if (wave.startTime === null) {wave.startTime = 0;} 37 | if (wave.endTime === null) {wave.endTime = duration;} 38 | wave.region = wavesurfer.addRegion({ 39 | start: wave.startTime, 40 | end: wave.endTime, 41 | color: 'hsla(400, 100%, 30%, 0.2)', 42 | }); 43 | wavesurfer.on('region-updated', function(obj) { 44 | wave.startTime = obj.start; 45 | wave.endTime = obj.end; 46 | }); 47 | wavesurfer.on('region-update-end', function(obj) { 48 | wave.sendRegion(); 49 | }); 50 | 51 | var timeline = Object.create(WaveSurfer.Timeline); 52 | var waveContainer = $(wave.container); 53 | var timelineContainer = waveContainer.parents().children(".waveform-timeline")[0]; 54 | timeline.init({ 55 | wavesurfer: wavesurfer, 56 | container: timelineContainer 57 | }); 58 | }); 59 | }; 60 | 61 | Wave.prototype.reload = function() { 62 | this.load(this.soundUrl); 63 | this.loadedAfterCollapse = true; 64 | } 65 | 66 | Wave.prototype.clear = function() { 67 | this.startTime = null; 68 | this.endTime = null; 69 | this.duration = null; 70 | this.soundUrl = null; 71 | this.loadedAfterCollapse = false; 72 | } 73 | 74 | Wave.prototype.setStart = function(startTime) { 75 | this.startTime = startTime; 76 | this.region.start = startTime; 77 | this.region.onResize(0, 'start'); 78 | }; 79 | 80 | Wave.prototype.setEnd = function(endTime) { 81 | this.endTime = endTime; 82 | this.region.end = endTime; 83 | this.region.onResize(0); 84 | }; 85 | 86 | Wave.prototype.restartRegion = function () { 87 | this.setStart(0); 88 | this.setEnd(this.duration); 89 | }; 90 | 91 | Wave.prototype.sendRegion = function () { 92 | var trackId = this.trackEl.index(); 93 | socket.emit('waveRegion', [trackId, this.startTime, this.endTime]); 94 | console.log('send wave region'); 95 | }; -------------------------------------------------------------------------------- /static/sounds/drum-samples/TR808/hihat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/drum-samples/TR808/hihat.mp3 -------------------------------------------------------------------------------- /static/sounds/drum-samples/TR808/hihat.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/drum-samples/TR808/hihat.ogg -------------------------------------------------------------------------------- /static/sounds/drum-samples/TR808/kick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/drum-samples/TR808/kick.mp3 -------------------------------------------------------------------------------- /static/sounds/drum-samples/TR808/kick.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/drum-samples/TR808/kick.ogg -------------------------------------------------------------------------------- /static/sounds/drum-samples/TR808/snare.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/drum-samples/TR808/snare.mp3 -------------------------------------------------------------------------------- /static/sounds/drum-samples/TR808/snare.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/drum-samples/TR808/snare.ogg -------------------------------------------------------------------------------- /static/sounds/impulse-responses/irHall.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/impulse-responses/irHall.ogg -------------------------------------------------------------------------------- /static/sounds/impulse-responses/matrix-reverb2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Web-Multi-Media/multi-web-audio-sequencer/080d7233228f62b9f2f0f2d9a94d06e8711c3930/static/sounds/impulse-responses/matrix-reverb2.wav -------------------------------------------------------------------------------- /static/stylesheets/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightblue; 3 | } 4 | 5 | .pad { 6 | margin-top: 1px; 7 | width: 2.0vw; 8 | height: 29px; 9 | /* height: 4vh;*/ 10 | display: inline-block; 11 | background: rgb(182, 173, 56); 12 | border-radius: 2px; 13 | cursor: pointer; 14 | } 15 | 16 | .selected { 17 | background: rgb(76, 62, 112); 18 | } 19 | 20 | .playing { 21 | background: radial-gradient(#FCFCFC, #E3E3E3, #B6B6B6); 22 | } 23 | 24 | .playing.selected { 25 | background: radial-gradient(#E6FFE6, #99FF99, #66FF66); 26 | } 27 | 28 | .noselect { 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } 36 | 37 | #play-pause { 38 | margin: 10px 0; 39 | } 40 | 41 | .control { 42 | margin: 10px 0; 43 | } 44 | 45 | .instruments { 46 | border-width: 1px; 47 | border-style: groove; 48 | border-radius: 10px; 49 | border-color: rgb(6, 69, 88); 50 | background-color:rgb(26, 87, 167); 51 | font: black; 52 | } 53 | 54 | .instrument, .instruments, #search-container, .wave, .tempoBorder, .wave, #room-container, #chat-container, #export-container { 55 | margin-top: 7px; 56 | border-width: 1px; 57 | border-style: groove; 58 | border-radius: 10px; 59 | border-color: rgb(41, 35, 66); 60 | background-color:rgb(47, 96, 156); 61 | font: black; 62 | } 63 | 64 | .instrument { 65 | padding: 2px; 66 | margin: 2px; 67 | } 68 | 69 | .section { 70 | padding: 10px; 71 | } 72 | 73 | .tempo-container { 74 | margin: 10px 10px; 75 | } 76 | 77 | #tempo-input { 78 | margin: 0 10px; 79 | line-height: 30px; 80 | border-radius: 3px; 81 | font-size: 25px; 82 | width: 60px; 83 | vertical-align: middle; 84 | } 85 | 86 | .tempo-btn { 87 | margin: 0 5px; 88 | } 89 | 90 | .drag-me { 91 | width: 30px; 92 | display: inline-block; 93 | height: 30px; 94 | border-radius: 15px; 95 | background: #BADA55; 96 | font-size: 10px; 97 | position: absolute; 98 | text-align: center; 99 | padding-top: 7px; 100 | } 101 | 102 | .instrument.drop-over { 103 | /* border-width: 1px;*/ 104 | border-style: groove; 105 | border-color: greenyellow; 106 | } 107 | 108 | /* 109 | .deleteTrackButton { 110 | padding-left: 0.5vw; 111 | padding-top: 0.15vw; 112 | width: 2.4vw; 113 | height:30px; 114 | height: 4vh; 115 | } 116 | */ 117 | 118 | wave { 119 | width: 101%; 120 | } 121 | 122 | a { 123 | color: black; 124 | } 125 | 126 | .jumbotron { 127 | background-color:rgb(47, 96, 156); 128 | margin-bottom: 10px; 129 | } 130 | 131 | 132 | /*Triangle dropdown*/ 133 | .glyphicon-chevron-right { 134 | position: relative; 135 | } 136 | 137 | .rotation { 138 | transform: rotate(90deg); 139 | } 140 | 141 | .my-box-white { 142 | border-radius: 4px; 143 | display: inline-block; 144 | padding: 6px 12px; 145 | margin-bottom: 0; 146 | font-size: 14px; 147 | font-weight: 400; 148 | line-height: 1.42857143; 149 | text-align: center; 150 | white-space: nowrap; 151 | vertical-align: middle; 152 | border: 1px solid transparent; 153 | } 154 | 155 | .my-box { 156 | color: #fff; 157 | background-color: #5cb85c; 158 | border-color: #4cae4c; 159 | border-radius: 4px; 160 | display: inline-block; 161 | padding: 6px 12px; 162 | margin-bottom: 0; 163 | font-size: 14px; 164 | font-weight: 400; 165 | line-height: 1.42857143; 166 | text-align: center; 167 | white-space: nowrap; 168 | vertical-align: middle; 169 | border: 1px solid transparent; 170 | } 171 | 172 | .dial { 173 | margin:0; 174 | height: 30px; 175 | } 176 | 177 | .chatArea, login page { 178 | /* width: 500px; */ 179 | height: 175px; 180 | overflow: auto 181 | } 182 | 183 | 184 | /* HOME PAGE */ 185 | .room-button { 186 | width: 100%; 187 | height: 100%; 188 | padding-top: 10px; 189 | padding-bottom: 12px; 190 | border-radius: 15px; 191 | background-color: rgb(26, 87, 167); 192 | } 193 | 194 | .row.display-flex { 195 | display: flex; 196 | flex-wrap: wrap; 197 | } 198 | .thumbnail { 199 | height: 100%; 200 | } 201 | 202 | .info-user { 203 | margin-top: -5px; 204 | height: 90px; 205 | overflow: auto; 206 | background-color: rgb(26, 103, 167); 207 | border-radius: 3px; 208 | } 209 | 210 | .info-last-time { 211 | float: right; 212 | font-size: 10px; 213 | margin-top: 10px; 214 | margin-right: 5px; 215 | margin-bottom: 5px; 216 | } 217 | 218 | .slider { 219 | width: 315px !important; 220 | } 221 | 222 | .room-card { 223 | margin-top: 10px; 224 | } 225 | 226 | .mute-track, .solo-track, .deleteTrackButton { 227 | border-radius: 5px; 228 | width: 20px; 229 | padding: 3px; 230 | font-size: 12px; 231 | margin-top: -22px; 232 | margin-left: 4px; 233 | } 234 | 235 | .deleteTrackButton { 236 | margin-left: 6px; 237 | } 238 | 239 | .mute-track:hover:not(.active), .solo-track:hover:not(.active) { 240 | background-color: rgb(51, 122, 183); 241 | } -------------------------------------------------------------------------------- /views/home.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Multi Web Audio Sequencer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 |
    16 |
    17 |

    Multi Web Audio Sequencer

    18 |

    Choose a Room

    19 |
    20 |
    21 |
    22 | 23 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Multi Web Audio Sequencer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    21 |

    Multi Web Audio Sequencer

    22 |
    23 |
    24 |
    25 | 26 | 27 |
    28 | 29 | 30 |
    31 |
    32 | Room: 33 | 1 34 | 2 35 | 3 36 | 4 37 | 5 38 | 6 39 | 7 40 | 8 41 |    42 | Nickname: 43 |
    44 | 45 | 46 |
    47 |
    48 |
    49 | 50 | 51 |
    52 |
    53 | 56 |    59 | 60 | 61 | 62 | 63 | 64 |    65 | 66 | 67 |
    68 | 69 | 70 |
    71 |
    72 | <%if (adminClient) { %>    73 | 74 | 75 | 76 | 77 | 80 | 82 | 83 | 84 | <% } %> 85 |
    86 |
    87 | 88 | 89 |
    90 |
    91 |
    92 |
    93 |
    94 | 95 | 101 |
    102 |
    103 |
    104 |
    105 | 106 | 107 |
    108 |
    109 |
    110 | 111 | 112 |
    113 |   114 | 129 |
    130 | Duration Filter (sec)   131 | 140 |
    141 |
    142 |
    143 |
    144 |
    145 |
    146 | 147 | 148 |
    149 |
    150 |
    151 |
      152 |
      153 | 154 |
      155 |
      156 | 157 | 158 |
      159 |
      160 | 161 |
      162 |
      163 |
      164 |
      165 | 166 | 167 | 191 | 192 |
      193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 335 | 336 | 337 | 338 | 339 | --------------------------------------------------------------------------------