├── .codeclimate.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .scrutinizer.yml ├── LICENSE ├── README.md ├── build └── minify-resources.php ├── composer.json ├── composer.lock ├── config-sample.php ├── dependency-checker.json ├── gitamp.sample.service ├── phpcs.xml.dist ├── phpunit.xml.dist ├── public ├── css │ ├── main.css │ ├── main.min.css │ └── themes │ │ └── default.css ├── images │ ├── GitAmp-logo-black.png │ ├── GitAmp-logo-white.png │ ├── GitAmp-neon-logo.png │ ├── electric-guitar.png │ ├── favicon.png │ ├── neon-g-favicon_256.ico │ ├── speaker-muted.svg │ └── speaker.svg ├── index.html ├── js │ ├── main.js │ └── main.min.js ├── robots.txt └── sounds │ ├── celesta │ ├── c001.mp3 │ ├── c001.ogg │ ├── c002.mp3 │ ├── c002.ogg │ ├── c003.mp3 │ ├── c003.ogg │ ├── c004.mp3 │ ├── c004.ogg │ ├── c005.mp3 │ ├── c005.ogg │ ├── c006.mp3 │ ├── c006.ogg │ ├── c007.mp3 │ ├── c007.ogg │ ├── c008.mp3 │ ├── c008.ogg │ ├── c009.mp3 │ ├── c009.ogg │ ├── c010.mp3 │ ├── c010.ogg │ ├── c011.mp3 │ ├── c011.ogg │ ├── c012.mp3 │ ├── c012.ogg │ ├── c013.mp3 │ ├── c013.ogg │ ├── c014.mp3 │ ├── c014.ogg │ ├── c015.mp3 │ ├── c015.ogg │ ├── c016.mp3 │ ├── c016.ogg │ ├── c017.mp3 │ ├── c017.ogg │ ├── c018.mp3 │ ├── c018.ogg │ ├── c019.mp3 │ ├── c019.ogg │ ├── c020.mp3 │ ├── c020.ogg │ ├── c021.mp3 │ ├── c021.ogg │ ├── c022.mp3 │ ├── c022.ogg │ ├── c023.mp3 │ ├── c023.ogg │ ├── c024.mp3 │ ├── c024.ogg │ ├── c025.mp3 │ ├── c025.ogg │ ├── c026.mp3 │ ├── c026.ogg │ ├── c027.mp3 │ └── c027.ogg │ ├── clav │ ├── c001.mp3 │ ├── c001.ogg │ ├── c002.mp3 │ ├── c002.ogg │ ├── c003.mp3 │ ├── c003.ogg │ ├── c004.mp3 │ ├── c004.ogg │ ├── c005.mp3 │ ├── c005.ogg │ ├── c006.mp3 │ ├── c006.ogg │ ├── c007.mp3 │ ├── c007.ogg │ ├── c008.mp3 │ ├── c008.ogg │ ├── c009.mp3 │ ├── c009.ogg │ ├── c010.mp3 │ ├── c010.ogg │ ├── c011.mp3 │ ├── c011.ogg │ ├── c012.mp3 │ ├── c012.ogg │ ├── c013.mp3 │ ├── c013.ogg │ ├── c014.mp3 │ ├── c014.ogg │ ├── c015.mp3 │ ├── c015.ogg │ ├── c016.mp3 │ ├── c016.ogg │ ├── c017.mp3 │ ├── c017.ogg │ ├── c018.mp3 │ ├── c018.ogg │ ├── c019.mp3 │ ├── c019.ogg │ ├── c020.mp3 │ ├── c020.ogg │ ├── c021.mp3 │ ├── c021.ogg │ ├── c022.mp3 │ ├── c022.ogg │ ├── c023.mp3 │ ├── c023.ogg │ ├── c024.mp3 │ ├── c024.ogg │ ├── c025.mp3 │ ├── c025.ogg │ ├── c026.mp3 │ ├── c026.ogg │ ├── c027.mp3 │ └── c027.ogg │ ├── egg │ ├── celesta.mp3 │ ├── celesta.ogg │ ├── clav.mp3 │ ├── clav.ogg │ ├── swell.mp3 │ └── swell.ogg │ └── swells │ ├── swell1.mp3 │ ├── swell1.ogg │ ├── swell2.mp3 │ ├── swell2.ogg │ ├── swell3.mp3 │ └── swell3.ogg ├── server.php ├── src ├── Configuration.php ├── Event │ ├── BaseEvent.php │ ├── Event.php │ ├── Factory.php │ └── GitHub │ │ ├── CreateEvent.php │ │ ├── ForkEvent.php │ │ ├── IssueCommentEvent.php │ │ ├── IssuesEvent.php │ │ ├── PullRequestEvent.php │ │ ├── PushEvent.php │ │ └── WatchEvent.php ├── Exception │ ├── DecodingFailed.php │ ├── Exception.php │ ├── RequestFailed.php │ └── UnknownEvent.php ├── Github │ ├── Credentials.php │ └── Token.php ├── Log │ └── LoggerFactory.php ├── Presentation │ ├── Information.php │ ├── Ring.php │ ├── Sound │ │ ├── BaseSound.php │ │ ├── Celesta.php │ │ ├── CelestaEgg.php │ │ ├── Clav.php │ │ ├── ClavEgg.php │ │ ├── Swell.php │ │ └── SwellEgg.php │ └── Type.php ├── Provider │ ├── GitHub.php │ └── Listener.php ├── Response │ ├── Factory.php │ └── Results.php ├── Server.php ├── ServerAddress.php ├── SslServerAddress.php └── Websocket │ └── Handler.php └── tests ├── ConfigurationTest.php ├── Data ├── invalid.json └── valid.json ├── Event ├── BaseEventTest.php ├── FactoryTest.php └── GitHub │ ├── CreateEventTest.php │ ├── ForkEventTest.php │ ├── IssueCommentEventTest.php │ ├── IssuesEventTest.php │ ├── PullRequestEventTest.php │ ├── PushEventTest.php │ └── WatchEventTest.php ├── Exception └── UnknownEventTest.php ├── Fakes └── HttpClient │ ├── MockFailedResponseInterceptor.php │ ├── MockSuccessfulResponseInterceptor.php │ └── MockThrowingResponseInterceptor.php ├── Github └── TokenTest.php ├── Log └── LoggerFactoryTest.php ├── Provider └── GitHubTest.php ├── Response ├── FactoryTest.php └── ResultsTest.php ├── ServerAddressTest.php ├── ServerTest.php ├── SslServerAddressTest.php ├── Websocket └── HandlerTest.php └── bootstrap.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - php 8 | fixme: 9 | enabled: true 10 | phpmd: 11 | enabled: true 12 | ratings: 13 | paths: 14 | - "**.php" 15 | exclude_paths: 16 | - tests/ 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.{diff,md}] 10 | trim_trailing_whitespace = false 11 | 12 | [*.php] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [*.{js,json,html,twig}] 17 | indent_style = space 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.github export-ignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /vendor/ 3 | config.php 4 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - 'tests/*' 4 | checks: 5 | php: 6 | use_self_instead_of_fqcn: true 7 | uppercase_constants: true 8 | simplify_boolean_return: true 9 | remove_extra_empty_lines: true 10 | properties_in_camelcaps: true 11 | phpunit_assertions: true 12 | prefer_while_loop_over_for_loop: true 13 | parameters_in_camelcaps: true 14 | param_doc_comment_if_not_inferrable: true 15 | overriding_parameter: true 16 | optional_parameters_at_the_end: true 17 | newline_at_end_of_file: true 18 | line_length: 19 | max_length: '120' 20 | encourage_single_quotes: true 21 | encourage_postdec_operator: true 22 | classes_in_camel_caps: true 23 | check_method_contracts: 24 | verify_interface_like_constraints: true 25 | verify_documented_constraints: true 26 | verify_parent_constraints: true 27 | avoid_unnecessary_concatenation: true 28 | avoid_todo_comments: true 29 | avoid_perl_style_comments: true 30 | avoid_multiple_statements_on_same_line: true 31 | avoid_fixme_comments: true 32 | align_assignments: true 33 | coding_style: 34 | php: 35 | spaces: 36 | around_operators: 37 | concatenation: true 38 | tools: 39 | external_code_coverage: true 40 | build: 41 | environment: 42 | php: 43 | version: "7.4" 44 | nodes: 45 | analysis: 46 | tests: 47 | override: 48 | - php-scrutinizer-run 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ekin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![`ekinhbayar/gitamp`](./public/images/GitAmp-logo-black.png) 2 | 3 | --- 4 | [![CI Status](https://github.com/ekinhbayar/gitamp/workflows/CI/badge.svg)](https://github.com/ekinhbayar/gitamp/actions?query=workflow%3ACI) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ekinhbayar/gitamp/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ekinhbayar/gitamp/?branch=master) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/ekinhbayar/gitamp/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/ekinhbayar/gitamp/?branch=master) 7 | [![Code Climate](https://codeclimate.com/github/ekinhbayar/gitamp/badges/gpa.svg)](https://codeclimate.com/github/ekinhbayar/gitamp) 8 | 9 | Listen to music generated by events across github. 10 | 11 | Made with [amphp](http://amphp.org/) magic `<3` 12 | 13 | Clone of [github.audio](https://github.audio). 14 | 15 | Requires: 16 | 17 | - PHP 7.4+ 18 | 19 | ## Usage 20 | 21 | - Run `composer update` 22 | - Copy the config.sample.php file to config.php and change the settings 23 | - Run the server using `php server.php` 24 | - Open your browser and go to http://localhost:1337 (for default settings) 25 | - Profit! 26 | 27 | ## GitAmp as a Service 28 | 29 | To run GitAmp as a systemd unit: 30 | 31 | - Copy the [gitamp.sample.service](https://github.com/ekinhbayar/gitamp/blob/master/gitamp.sample.service) to `/etc/systemd/system/gitamp.service`. 32 | - Replace the paths with your installation location. 33 | - Enable it by running `systemctl enable gitamp` && start with `systemctl start gitamp` 34 | 35 | If you want to run it after reboots as well, symlink the service file under `multi-user.target.wants` via 36 | 37 | `ln -sf /etc/systemd/system/gitamp.service /etc/systemd/system/multi-user.target.wants/gitamp.service` 38 | 39 | ## Optional Dependencies 40 | 41 | For true non-blocking execution, install one of the following: 42 | 43 | - [`libevent`](https://pecl.php.net/package/libevent) PECL extension. 44 | - [`ev`](https://pecl.php.net/package/ev) PECL extension 45 | - [`php-uv`](https://github.com/bwoebi/php-uv) PHP extension. 46 | 47 | ## Issues 48 | 49 | All features requests, bug reports or questions can be posted in [GitHub issues](https://github.com/ekinhbayar/gitamp/issues). 50 | 51 | For security related reports please send a mail to gitamp-security@ekins.space instead of using GitHub's issues. 52 | -------------------------------------------------------------------------------- /build/minify-resources.php: -------------------------------------------------------------------------------- 1 | add(__DIR__ . '/../public/css/themes\default.css'); 12 | 13 | $cssMinifier->minify(__DIR__ . '/../public/css/main.min.css'); 14 | 15 | (new JS(__DIR__ . '/../public/js/main.js'))->minify(__DIR__ . '/../public/js/main.min.js'); 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ekinhbayar/gitamp", 3 | "description": "Listen ambient music generated by events across github.", 4 | "keywords": [ 5 | "audio", 6 | "events", 7 | "github", 8 | "async", 9 | "non-blocking" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Ekin H. Bayar", 15 | "email": "me@ekins.space", 16 | "role": "Developer" 17 | }, 18 | { 19 | "name": "Pieter Hordijk", 20 | "email": "info@pieterhordijk.com", 21 | "homepage": "https://pieterhordijk.com", 22 | "role": "Developer" 23 | } 24 | ], 25 | "minimum-stability": "dev", 26 | "prefer-stable": true, 27 | "require": { 28 | "php": ">=7.4", 29 | "ext-json": "*", 30 | "amphp/amp": "^2", 31 | "amphp/byte-stream": "^1.8", 32 | "amphp/http": "^1.6", 33 | "amphp/http-client": "^4.5", 34 | "amphp/http-server": "^2.1", 35 | "amphp/http-server-router": "^1.0", 36 | "amphp/http-server-static-content": "^1.0.6", 37 | "amphp/log": "^1.1", 38 | "amphp/socket": "^1.1", 39 | "amphp/websocket": "^1.0", 40 | "amphp/websocket-server": "^2.0", 41 | "daverandom/exceptional-json": "^1.0.4", 42 | "league/uri": "^6.3", 43 | "league/uri-interfaces": "^2.1", 44 | "monolog/monolog": "^2", 45 | "nikic/fast-route": "^1", 46 | "psr/log": "^1" 47 | }, 48 | "require-dev": { 49 | "matthiasmullie/minify": "^1.3", 50 | "object-calisthenics/phpcs-calisthenics-rules": "^3.7", 51 | "phpunit/phpunit": "^9.3", 52 | "slevomat/coding-standard": "^6.0@dev", 53 | "squizlabs/php_codesniffer": "^3.5" 54 | }, 55 | "autoload": { 56 | "psr-4": { 57 | "ekinhbayar\\GitAmp\\": "src/" 58 | } 59 | }, 60 | "autoload-dev": { 61 | "psr-4": { 62 | "ekinhbayar\\GitAmpTests\\": "tests/" 63 | } 64 | }, 65 | "config": { 66 | "sort-packages": true 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config-sample.php: -------------------------------------------------------------------------------- 1 | addWebsocketAddress(Uri::createFromString('https://gitamp.audio')) 16 | ->bind(new ServerAddress('127.0.0.1', 1337)) 17 | ->addSpecialRepository('ekinhbayar/gitamp') 18 | ->addSpecialRepository('amphp/amp') 19 | ; 20 | -------------------------------------------------------------------------------- /dependency-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbol-whitelist" : [ 3 | "null", "true", "false", 4 | "static", "self", "parent", 5 | "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object" 6 | ], 7 | "php-core-extensions" : [ 8 | "Core", 9 | "date", 10 | "pcre", 11 | "Phar", 12 | "Reflection", 13 | "SPL", 14 | "standard" 15 | ], 16 | "scan-files" : ["bin/*.php"] 17 | } 18 | -------------------------------------------------------------------------------- /gitamp.sample.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=GitAmp as a service (GAAS) 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/path/to/php /path/to/gitamp/vendor/bin/aerys -c /path/to/gitamp/server.php -d 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | 13 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ./src 11 | ./tests 12 | ./config-sample.php 13 | ./server.php 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | error 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 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 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./src 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400|Josefin+Sans'; 2 | 3 | html, body, div, span, applet, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | a, abbr, acronym, address, big, cite, code, 6 | del, dfn, em, img, ins, kbd, q, s, samp, 7 | small, strike, strong, sub, sup, tt, var, 8 | b, u, i, center, 9 | dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, embed, 13 | figure, figcaption, footer, header, 14 | menu, nav, output, ruby, section, summary, 15 | time, mark, audio, video { 16 | margin: 0; 17 | padding: 0; 18 | border: 0; 19 | font: inherit; 20 | font-size: 100%; 21 | vertical-align: baseline; 22 | } 23 | 24 | /* HTML5 display-role reset for older browsers */ 25 | article, aside, details, figcaption, figure, 26 | footer, header, menu, nav, section { 27 | display: block; 28 | } 29 | 30 | body { 31 | line-height: 1; 32 | } 33 | 34 | ol, ul { 35 | list-style: none; 36 | } 37 | 38 | blockquote, q { 39 | quotes: none; 40 | } 41 | 42 | blockquote:before, blockquote:after, 43 | q:before, q:after { 44 | content: ''; 45 | } 46 | 47 | table { 48 | border-collapse: collapse; 49 | border-spacing: 0; 50 | } 51 | 52 | a { 53 | text-decoration: none; 54 | } 55 | 56 | * { 57 | box-sizing: border-box; 58 | margin: 0; 59 | padding: 0; 60 | } 61 | 62 | html,body { 63 | font-family: 'Source Sans Pro', sans-serif; 64 | font-weight: 400; 65 | font-size: 16px; 66 | height: 100%; 67 | width: 100%; 68 | } 69 | 70 | header { 71 | position: absolute; 72 | width: 100%; 73 | /*height: 35px;*/ 74 | color: #fff; 75 | font-family: 'Source Sans Pro', sans-serif; 76 | padding-left: 20px; 77 | padding-top: 20px; 78 | padding-bottom: 20px; 79 | box-sizing: border-box; 80 | height: 80px; 81 | z-index: 100; 82 | } 83 | 84 | .repo-link { 85 | color: white; 86 | } 87 | 88 | .header-text { 89 | float: left; 90 | font-size: 2em; 91 | line-height: 1em; 92 | } 93 | 94 | .offline-text { 95 | font-size: 0.4em; 96 | visibility: hidden; 97 | } 98 | 99 | .events-remaining { 100 | float:right; 101 | margin-right: 5%; 102 | margin-top: 30px; 103 | } 104 | 105 | .events-remaining-text, .events-remaining-value { 106 | font-size: 0.8em; 107 | visibility: hidden; 108 | } 109 | 110 | #volumeSlider { 111 | cursor:pointer; 112 | position: absolute; 113 | top: 30px; 114 | right: 40px; 115 | width: 100px; 116 | opacity: 0.3; 117 | border-radius: 5px; 118 | } 119 | 120 | #volumeSlider:hover { 121 | cursor:pointer; 122 | opacity: 0.9; 123 | } 124 | 125 | #area { 126 | width: 100%; 127 | position: relative; 128 | height: 100%; 129 | overflow: hidden; 130 | } 131 | 132 | svg { 133 | height: 100%; 134 | display: block; 135 | } 136 | 137 | svg text { 138 | color: #FFFFFF; 139 | } 140 | 141 | circle { 142 | fill-opacity: 0.8; 143 | } 144 | 145 | .online-users-div { 146 | text-align: center; 147 | position: absolute; 148 | bottom: 60px; 149 | width: 100%; 150 | margin: 0 auto; 151 | font-size: 0.9em; 152 | z-index: 1; 153 | opacity: 0.5; 154 | visibility: hidden; 155 | } 156 | 157 | .online-users-text { 158 | font-family: 'Source Sans Pro', sans-serif; 159 | font-size: 1em; 160 | } 161 | 162 | .site-description { 163 | font-size: 1em; 164 | line-height: 1.6em; 165 | width: 50%; 166 | margin: 50px auto 0; 167 | } 168 | 169 | @media only screen and (max-device-width: 480px) { 170 | .online-users-div { 171 | margin-left: 20px; 172 | text-align: left; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /public/css/main.min.css: -------------------------------------------------------------------------------- 1 | @import "https://fonts.googleapis.com/css?family=Source+Sans+Pro:400|Josefin+Sans";html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:''}table{border-collapse:collapse;border-spacing:0}a{text-decoration:none}*{box-sizing:border-box;margin:0;padding:0}html,body{font-family:'Source Sans Pro',sans-serif;font-weight:400;font-size:16px;height:100%;width:100%}header{position:absolute;width:100%;color:#fff;font-family:'Source Sans Pro',sans-serif;padding-left:20px;padding-top:20px;padding-bottom:20px;box-sizing:border-box;height:80px;z-index:100}.repo-link{color:#fff}.header-text{float:left;font-size:2em;line-height:1em}.offline-text{font-size:.4em;visibility:hidden}.events-remaining{float:right;margin-right:5%;margin-top:30px}.events-remaining-text,.events-remaining-value{font-size:.8em;visibility:hidden}#volumeSlider{cursor:pointer;position:absolute;top:30px;right:40px;width:100px;opacity:.3;border-radius:5px}#volumeSlider:hover{cursor:pointer;opacity:.9}#area{width:100%;position:relative;height:100%;overflow:hidden}svg{height:100%;display:block}svg text{color:#FFF}circle{fill-opacity:.8}.online-users-div{text-align:center;position:absolute;bottom:60px;width:100%;margin:0 auto;font-size:.9em;z-index:1;opacity:.5;visibility:hidden}.online-users-text{font-family:'Source Sans Pro',sans-serif;font-size:1em}.site-description{font-size:1em;line-height:1.6em;width:50%;margin:50px auto 0}@media only screen and (max-device-width:480px){.online-users-div{margin-left:20px;text-align:left}}html,body{background-color:#232323}a{color:#0091EA}svg{background-color:#232323}.label{font:1em 'Source Sans Pro',sans-serif;text-shadow:1px 1px 0 rgb(28,39,51),-1px -1px 0 rgb(28,39,51),1px -1px 0 rgb(28,39,51),-1px 1px 0 rgb(28,39,51),0 1px 0 rgb(28,39,51),1px 0 0 rgb(28,39,51),0 -1px 0 rgb(28,39,51),-1px 0 0 rgb(28,39,51)}.online-users-text{color:#E0E0E0}.event-1{fill:#22B65D}.event-2{fill:#8F19BB}.event-3{fill:#ADD913}.event-4{fill:#FF4901}.event-5{fill:#0184FF}.event-6{fill:#00C0C0}.event-7{fill:#E60062} -------------------------------------------------------------------------------- /public/css/themes/default.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | background-color: #232323; 3 | } 4 | 5 | a { 6 | color: #0091EA; 7 | } 8 | 9 | svg { 10 | background-color: #232323; 11 | } 12 | 13 | .label { 14 | font: 1.0em 'Source Sans Pro', sans-serif; 15 | text-shadow:1px 1px 0 rgb(28, 39, 51), 16 | -1px -1px 0 rgb(28, 39, 51), 17 | 1px -1px 0 rgb(28, 39, 51), 18 | -1px 1px 0 rgb(28, 39, 51), 19 | 0 1px 0 rgb(28, 39, 51), 20 | 1px 0 0 rgb(28, 39, 51), 21 | 0 -1px 0 rgb(28, 39, 51), 22 | -1px 0 0 rgb(28, 39, 51); 23 | } 24 | 25 | .online-users-text { 26 | color: #E0E0E0; 27 | } 28 | 29 | .event-1 { 30 | fill: #22B65D; 31 | } 32 | 33 | .event-2 { 34 | fill: #8F19BB; 35 | } 36 | 37 | .event-3 { 38 | fill: #ADD913; 39 | } 40 | 41 | .event-4 { 42 | fill: #FF4901; 43 | } 44 | 45 | .event-5 { 46 | fill: #0184FF; 47 | } 48 | 49 | .event-6 { 50 | fill: #00C0C0; 51 | } 52 | 53 | .event-7 { 54 | fill: #E60062; 55 | } 56 | -------------------------------------------------------------------------------- /public/images/GitAmp-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/GitAmp-logo-black.png -------------------------------------------------------------------------------- /public/images/GitAmp-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/GitAmp-logo-white.png -------------------------------------------------------------------------------- /public/images/GitAmp-neon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/GitAmp-neon-logo.png -------------------------------------------------------------------------------- /public/images/electric-guitar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/electric-guitar.png -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/neon-g-favicon_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekinhbayar/gitamp/65c5937901011b0e40c5388b564bdc8f4692893f/public/images/neon-g-favicon_256.ico -------------------------------------------------------------------------------- /public/images/speaker-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /public/images/speaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | GitAmp 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |

38 | 39 | GitAmp 40 | 41 |

42 |
43 | 44 | events remaining in queue 45 |
46 |
47 |
48 | 49 |
50 |
51 |

people listening

52 |
53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | const GitAmp = (function(exports, $) { 2 | 'use strict'; 3 | 4 | /** 5 | * AudioPlayer 6 | */ 7 | const AudioPlayer = (function() { 8 | // something somewhere needs a global volume variable 9 | // not sure what thing it is, but adding this line works 10 | exports.volume = localStorage.getItem('volume'); 11 | 12 | const maxPitch = 100.0; 13 | const logUsed = 1.0715307808111486871978099; 14 | 15 | const maximumSimultaneousNotes = 2; 16 | const soundLength = 300; 17 | 18 | function AudioPlayer() { 19 | this.currentlyPlayingSounds = 0; 20 | 21 | this.sounds = { 22 | celesta: this.initializeCelesta(), 23 | clav: this.initializeClav(), 24 | swells: this.initializeSwells(), 25 | easterEggs: this.initializeEasterEggs() 26 | }; 27 | 28 | //noinspection JSUnresolvedVariable 29 | exports.Howler.volume(localStorage.getItem('volume')); 30 | } 31 | 32 | AudioPlayer.prototype.initializeCelesta = function() { 33 | const sounds = []; 34 | 35 | for (let i = 1; i <= 24; i++) { 36 | let filename = (i > 9) ? 'c0' + i : 'c00' + i; 37 | 38 | //noinspection JSUnresolvedFunction 39 | sounds.push(new Howl({ 40 | src : [ 41 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.ogg', 42 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + filename + '.mp3' 43 | ], 44 | volume : 0.7, 45 | buffer: true 46 | })); 47 | } 48 | 49 | return sounds; 50 | }; 51 | 52 | AudioPlayer.prototype.initializeClav = function() { 53 | const sounds = []; 54 | 55 | for (let i = 1; i <= 24; i++) { 56 | let filename = (i > 9) ? 'c0' + i : 'c00' + i; 57 | 58 | //noinspection JSUnresolvedFunction 59 | sounds.push(new Howl({ 60 | src : [ 61 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.ogg', 62 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + filename + '.mp3' 63 | ], 64 | volume : 0.7, 65 | buffer: true 66 | })); 67 | } 68 | 69 | return sounds; 70 | }; 71 | 72 | AudioPlayer.prototype.initializeSwells = function() { 73 | const sounds = []; 74 | 75 | for (let i = 1; i <= 3; i++) { 76 | //noinspection JSUnresolvedFunction 77 | sounds.push(new Howl({ 78 | src : [ 79 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.ogg', 80 | 'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.mp3' 81 | ], 82 | volume : 0.7, 83 | buffer: true 84 | })); 85 | } 86 | 87 | return sounds; 88 | }; 89 | 90 | AudioPlayer.prototype.initializeEasterEggs = function() { 91 | const sounds = {}; 92 | 93 | const eggs = ['celesta', 'clav', 'swell']; 94 | 95 | for (let i = 0; i < eggs.length; i++) { 96 | //noinspection JSUnresolvedFunction 97 | sounds[eggs[i]] = new Howl({ 98 | src : [ 99 | '/sounds/egg/' + eggs[i] + '.ogg', 100 | '/sounds/egg/' + eggs[i] + '.mp3' 101 | ], 102 | volume : 0.7, 103 | buffer: true 104 | }); 105 | } 106 | 107 | return sounds; 108 | }; 109 | 110 | AudioPlayer.prototype.getSoundIndex = function(size, type) { 111 | const pitch = 100 - Math.min(maxPitch, Math.log(size + logUsed) / Math.log(logUsed)); 112 | let index = Math.floor(pitch / 100.0 * this.sounds[type].length); 113 | 114 | index += Math.floor(Math.random() * 4) - 2; 115 | index = Math.min(this.sounds[type].length - 1, index); 116 | index = Math.max(1, index); 117 | 118 | return index; 119 | }; 120 | 121 | AudioPlayer.prototype.playSound = function(sound) { 122 | if (this.currentlyPlayingSounds >= maximumSimultaneousNotes) { 123 | return; 124 | } 125 | 126 | sound.play(); 127 | 128 | this.currentlyPlayingSounds++; 129 | 130 | setTimeout(function() { 131 | this.currentlyPlayingSounds--; 132 | }.bind(this), soundLength); 133 | }; 134 | 135 | AudioPlayer.prototype.playCelesta = function(size) { 136 | this.playSound(this.sounds.celesta[this.getSoundIndex(size, 'celesta')]); 137 | }; 138 | 139 | AudioPlayer.prototype.playClav = function(size) { 140 | this.playSound(this.sounds.clav[this.getSoundIndex(size, 'clav')]); 141 | }; 142 | 143 | AudioPlayer.prototype.playSwell = function() { 144 | this.playSound(this.sounds.swells[Math.round(Math.random() * (this.sounds.swells.length - 1))]); 145 | }; 146 | 147 | AudioPlayer.prototype.playCelestaEgg = function() { 148 | //noinspection JSUnresolvedVariable 149 | this.playSound(this.sounds.easterEggs.celesta); 150 | }; 151 | 152 | AudioPlayer.prototype.playClavEgg = function() { 153 | //noinspection JSUnresolvedVariable 154 | this.playSound(this.sounds.easterEggs.clav); 155 | }; 156 | 157 | AudioPlayer.prototype.playSwellEgg = function() { 158 | //noinspection JSUnresolvedVariable 159 | this.playSound(this.sounds.easterEggs.swell); 160 | }; 161 | 162 | return AudioPlayer; 163 | }()); 164 | 165 | /** 166 | * Gui 167 | */ 168 | const Gui = (function() { 169 | const scaleFactor = 6; 170 | const textColor = '#ffffff'; 171 | const maxLife = 20000; 172 | 173 | function Event(event, svg) { 174 | this.event = event; 175 | this.svg = svg; 176 | } 177 | 178 | Event.prototype.getSize = function() { 179 | return Math.max(Math.sqrt(Math.abs(this.event.getPayload().length)) * scaleFactor, 3); 180 | }; 181 | 182 | Event.prototype.getText = function() { 183 | return this.event.getMessage(); 184 | }; 185 | 186 | Event.prototype.getClassName = function() { 187 | return 'event-' + this.event.getType(); 188 | }; 189 | 190 | Event.prototype.getRingAnimationDuration = function() { 191 | return this.event.getRingAnimationDuration(); 192 | }; 193 | 194 | Event.prototype.getRingRadius = function() { 195 | return this.event.getRingRadius(); 196 | }; 197 | 198 | Event.prototype.draw = function(width, height) { 199 | let no_label = false; 200 | let size = this.getSize(); 201 | 202 | const self = this; 203 | 204 | //noinspection JSUnresolvedFunction 205 | Math.seedrandom(this.event.getPayload()); 206 | let x = Math.random() * (width - size) + size; 207 | let y = Math.random() * (height - size) + size; 208 | 209 | let circle_group = this.svg.append('g') 210 | .classed(this.getClassName(), true) 211 | .attr('transform', 'translate(' + x + ', ' + y + ')') 212 | .style('opacity', 1); 213 | 214 | let ring = circle_group.append('circle'); 215 | ring.attr({r: size, stroke: 'none'}); 216 | ring.transition() 217 | .attr('r', size + this.getRingRadius()) 218 | .style('opacity', 0) 219 | .ease(Math.sqrt) 220 | .duration(this.getRingAnimationDuration()) 221 | .remove(); 222 | 223 | let circle_container = circle_group.append('a'); 224 | circle_container.attr('xlink:href', this.event.getUrl()); 225 | circle_container.attr('target', '_blank'); 226 | circle_container.attr('fill', textColor); 227 | 228 | let circle = circle_container.append('circle'); 229 | circle.classed(this.getClassName(), true); 230 | circle.attr('r', size) 231 | .transition() 232 | .duration(maxLife) 233 | .style('opacity', 0) 234 | .remove(); 235 | 236 | circle_container.on('mouseover', function() { 237 | circle_container.append('text') 238 | .text(self.getText()) 239 | .classed('label', true) 240 | .attr('text-anchor', 'middle') 241 | .attr('font-size', '0.8em') 242 | .transition() 243 | .delay(1000) 244 | .style('opacity', 0) 245 | .duration(2000) 246 | .each(function() { no_label = true; }) 247 | .remove(); 248 | }); 249 | 250 | circle_container.append('text') 251 | .text(this.getText()) 252 | .classed('article-label', true) 253 | .attr('text-anchor', 'middle') 254 | .attr('font-size', '0.8em') 255 | .transition() 256 | .delay(2000) 257 | .style('opacity', 0) 258 | .duration(5000) 259 | .each(function() { no_label = true; }) 260 | .remove(); 261 | }; 262 | 263 | function Gui() { 264 | //noinspection JSUnresolvedVariable 265 | this.svg = exports.d3.select('#area').append('svg'); 266 | 267 | exports.addEventListener('resize', this.resize.bind(this)); 268 | 269 | this.setupVolumeSlider(); 270 | this.resize(); 271 | } 272 | 273 | Gui.prototype.setupVolumeSlider = function() { 274 | //noinspection JSUnresolvedFunction 275 | $('#volumeSlider').slider({ 276 | max: 100, 277 | min: 0, 278 | value: localStorage.getItem('volume') * 100, 279 | slide: function (event, ui) { 280 | //noinspection JSUnresolvedVariable 281 | exports.Howler.volume(ui.value/100.0); 282 | 283 | localStorage.setItem('volume', ui.value/100.0); 284 | }, 285 | change: function (event, ui) { 286 | //noinspection JSUnresolvedVariable 287 | exports.Howler.volume(ui.value/100.0); 288 | 289 | localStorage.setItem('volume', ui.value/100.0); 290 | } 291 | }); 292 | }; 293 | 294 | Gui.prototype.getWidth = function() { 295 | return exports.innerWidth; 296 | }; 297 | 298 | Gui.prototype.getHeight = function() { 299 | return exports.innerHeight; 300 | }; 301 | 302 | Gui.prototype.resize = function() { 303 | this.svg.attr('width', this.getWidth()); 304 | this.svg.attr('height', this.getHeight()); 305 | }; 306 | 307 | Gui.prototype.drawEvent = function(event) { 308 | if (document.hidden) { 309 | return; 310 | } 311 | 312 | new Event(event, this.svg).draw(this.getWidth(), this.getHeight()); 313 | 314 | // Remove HTML of decayed events 315 | // Keep it less than 50 316 | let $area = $('#area'); 317 | if($area.find('svg g').length > 50){ 318 | $area.find('svg g:lt(10)').remove(); 319 | } 320 | }; 321 | 322 | return Gui; 323 | }()); 324 | 325 | /** 326 | * ConnectedUsersMessage 327 | */ 328 | function ConnectedUsersMessage(response) { 329 | //noinspection JSUnresolvedVariable 330 | this.count = response.connectedUsers; 331 | } 332 | 333 | ConnectedUsersMessage.prototype.getCount = function() { 334 | return this.count; 335 | }; 336 | 337 | /** 338 | * EventMessage 339 | */ 340 | function EventMessage(event) { 341 | this.event = event; 342 | } 343 | 344 | EventMessage.prototype.getId = function() { 345 | //noinspection JSUnresolvedVariable 346 | return this.event.id; 347 | }; 348 | 349 | EventMessage.prototype.getType = function() { 350 | //noinspection JSUnresolvedVariable 351 | return this.event.type; 352 | }; 353 | 354 | EventMessage.prototype.getUrl = function() { 355 | //noinspection JSUnresolvedVariable 356 | return this.event.information.url; 357 | }; 358 | 359 | EventMessage.prototype.getPayload = function() { 360 | //noinspection JSUnresolvedVariable 361 | return this.event.information.payload; 362 | }; 363 | 364 | EventMessage.prototype.getMessage = function() { 365 | //noinspection JSUnresolvedVariable 366 | return this.event.information.message; 367 | }; 368 | 369 | EventMessage.prototype.getRingAnimationDuration = function() { 370 | //noinspection JSUnresolvedVariable 371 | return this.event.ring.animationDuration; 372 | }; 373 | 374 | EventMessage.prototype.getRingRadius = function() { 375 | //noinspection JSUnresolvedVariable 376 | return this.event.ring.radius; 377 | }; 378 | 379 | EventMessage.prototype.getSoundType = function() { 380 | //noinspection JSUnresolvedVariable 381 | return this.event.sound.type; 382 | }; 383 | 384 | EventMessage.prototype.getSoundSize = function() { 385 | //noinspection JSUnresolvedVariable 386 | return this.event.sound.size; 387 | }; 388 | 389 | /** 390 | * EventMessageCollection 391 | */ 392 | function EventMessageCollection(response) { 393 | this.events = []; 394 | 395 | for (let i = 0; i < response.length; i++) { 396 | this.events.push(new EventMessage(response[i])); 397 | } 398 | } 399 | 400 | EventMessageCollection.prototype.forEach = function(callback) { 401 | for (let i = 0; i < this.events.length; i++) { 402 | callback(this.events[i]); 403 | } 404 | }; 405 | 406 | /** 407 | * EventMessagesFactory 408 | */ 409 | function EventMessagesFactory () { 410 | } 411 | 412 | EventMessagesFactory.prototype.build = function(response) { 413 | const parsedResponse = JSON.parse(response.data); 414 | 415 | if (parsedResponse.hasOwnProperty('connectedUsers')) { 416 | return new ConnectedUsersMessage(parsedResponse); 417 | } 418 | 419 | return new EventMessageCollection(parsedResponse); 420 | }; 421 | 422 | /** 423 | * EventQueue 424 | */ 425 | function EventQueue() { 426 | this.queue = []; 427 | } 428 | 429 | EventQueue.prototype.append = function(eventMessages) { 430 | eventMessages.forEach(function(event) { 431 | if (this.exists(event)) { 432 | return; 433 | } 434 | 435 | this.queue.push(event); 436 | }.bind(this)); 437 | 438 | if (this.queue.length > 1000) { 439 | this.queue = this.queue.slice(0, 1000); 440 | } 441 | }; 442 | 443 | EventQueue.prototype.exists = function(event) { 444 | for (let i = 0; i < this.queue.length; i++) { 445 | if (event.getId() === this.queue[i].getId()) { 446 | return true; 447 | } 448 | } 449 | 450 | return false; 451 | }; 452 | 453 | EventQueue.prototype.get = function() { 454 | return this.queue.shift(); 455 | }; 456 | 457 | EventQueue.prototype.count = function() { 458 | return this.queue.length; 459 | }; 460 | 461 | /** 462 | * Connection 463 | */ 464 | function Connection(eventMessageFactory) { 465 | this.eventMessageFactory = eventMessageFactory; 466 | 467 | this.connection = null; 468 | this.handlers = []; 469 | } 470 | 471 | Connection.prototype.start = function() { 472 | let protocol = 'ws://'; 473 | 474 | if (exports.location.protocol === "https:") { 475 | protocol = 'wss://'; 476 | } 477 | 478 | try { 479 | this.connection = new WebSocket(protocol + exports.location.host + '/ws'); 480 | 481 | this.connection.addEventListener('message', this.handleMessage.bind(this)); 482 | this.connection.addEventListener('open', this.handleOpen.bind(this)); 483 | this.connection.addEventListener('close', this.reconnect.bind(this)); 484 | this.connection.addEventListener('error', this.reconnect.bind(this)); 485 | } catch(e) { 486 | this.connection = null; 487 | } 488 | }; 489 | 490 | Connection.prototype.registerHandler = function(handler) { 491 | this.handlers.push(handler); 492 | }; 493 | 494 | Connection.prototype.handleMessage = function(response) { 495 | const message = this.eventMessageFactory.build(response); 496 | 497 | for (let i = 0; i < this.handlers.length; i++) { 498 | this.handlers[i](message); 499 | } 500 | }; 501 | 502 | Connection.prototype.handleOpen = function() { 503 | const elements = document.querySelectorAll('.online-users-div'); 504 | 505 | for (let i = 0; i < elements.length; i++) { 506 | elements[i].style.visibility = 'visible'; 507 | } 508 | }; 509 | 510 | Connection.prototype.reconnect = function() { 511 | // prevent piling up reconnect 512 | if (this.connection.readyState === 0 || this.connection.readyState === 1) { 513 | return; 514 | } 515 | 516 | setTimeout(function() { 517 | this.start(); 518 | }.bind(this), 5000); 519 | }; 520 | 521 | /** 522 | * Application 523 | */ 524 | function Application() { 525 | if (localStorage.getItem('volume') === null) { 526 | localStorage.setItem('volume', 0.6); 527 | } 528 | 529 | this.queue = new EventQueue(); 530 | this.audio = new AudioPlayer(); 531 | this.gui = new Gui(); 532 | } 533 | 534 | Application.prototype.run = function() { 535 | const connection = new Connection(new EventMessagesFactory()); 536 | 537 | connection.registerHandler(this.process.bind(this)); 538 | 539 | connection.start(); 540 | 541 | this.loop(); 542 | }; 543 | 544 | Application.prototype.process = function(message) { 545 | if (message instanceof ConnectedUsersMessage) { 546 | document.getElementsByClassName('online-users-count')[0].textContent = message.getCount(); 547 | 548 | return; 549 | } 550 | 551 | this.queue.append(message); 552 | }; 553 | 554 | Application.prototype.loop = function() { 555 | setTimeout(function() { 556 | this.loop(); 557 | 558 | if (!this.queue.count()) { 559 | return; 560 | } 561 | 562 | this.processEvent(this.queue.get()); 563 | 564 | document.getElementsByClassName('events-remaining-value')[0].textContent = this.queue.count(); 565 | }.bind(this), Math.floor(Math.random() * 1000) + 500); 566 | }; 567 | 568 | Application.prototype.processEvent = function(event) { 569 | if (!event.getPayload()) { 570 | return; 571 | } 572 | 573 | this.audio['play' + event.getSoundType()](event.getSoundSize()); 574 | 575 | this.gui.drawEvent(event); 576 | }; 577 | 578 | return Application; 579 | }(window, jQuery)); 580 | 581 | $(function() { 582 | new GitAmp().run(); 583 | }); 584 | -------------------------------------------------------------------------------- /public/js/main.min.js: -------------------------------------------------------------------------------- 1 | const GitAmp=(function(exports,$){'use strict';const AudioPlayer=(function(){exports.volume=localStorage.getItem('volume');const maxPitch=100.0;const logUsed=1.0715307808111486871978099;const maximumSimultaneousNotes=2;const soundLength=300;function AudioPlayer(){this.currentlyPlayingSounds=0;this.sounds={celesta:this.initializeCelesta(),clav:this.initializeClav(),swells:this.initializeSwells(),easterEggs:this.initializeEasterEggs()};exports.Howler.volume(localStorage.getItem('volume'))} 2 | AudioPlayer.prototype.initializeCelesta=function(){const sounds=[];for(let i=1;i<=24;i++){let filename=(i>9)?'c0'+i:'c00'+i;sounds.push(new Howl({src:['https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/'+filename+'.ogg','https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/'+filename+'.mp3'],volume:0.7,buffer:!0}))} 3 | return sounds};AudioPlayer.prototype.initializeClav=function(){const sounds=[];for(let i=1;i<=24;i++){let filename=(i>9)?'c0'+i:'c00'+i;sounds.push(new Howl({src:['https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/'+filename+'.ogg','https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/'+filename+'.mp3'],volume:0.7,buffer:!0}))} 4 | return sounds};AudioPlayer.prototype.initializeSwells=function(){const sounds=[];for(let i=1;i<=3;i++){sounds.push(new Howl({src:['https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell'+i+'.ogg','https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell'+i+'.mp3'],volume:0.7,buffer:!0}))} 5 | return sounds};AudioPlayer.prototype.initializeEasterEggs=function(){const sounds={};const eggs=['celesta','clav','swell'];for(let i=0;i=maximumSimultaneousNotes){return} 7 | sound.play();this.currentlyPlayingSounds++;setTimeout(function(){this.currentlyPlayingSounds--}.bind(this),soundLength)};AudioPlayer.prototype.playCelesta=function(size){this.playSound(this.sounds.celesta[this.getSoundIndex(size,'celesta')])};AudioPlayer.prototype.playClav=function(size){this.playSound(this.sounds.clav[this.getSoundIndex(size,'clav')])};AudioPlayer.prototype.playSwell=function(){this.playSound(this.sounds.swells[Math.round(Math.random()*(this.sounds.swells.length-1))])};AudioPlayer.prototype.playCelestaEgg=function(){this.playSound(this.sounds.easterEggs.celesta)};AudioPlayer.prototype.playClavEgg=function(){this.playSound(this.sounds.easterEggs.clav)};AudioPlayer.prototype.playSwellEgg=function(){this.playSound(this.sounds.easterEggs.swell)};return AudioPlayer}());const Gui=(function(){const scaleFactor=6;const textColor='#ffffff';const maxLife=20000;function Event(event,svg){this.event=event;this.svg=svg} 8 | Event.prototype.getSize=function(){return Math.max(Math.sqrt(Math.abs(this.event.getPayload().length))*scaleFactor,3)};Event.prototype.getText=function(){return this.event.getMessage()};Event.prototype.getClassName=function(){return'event-'+this.event.getType()};Event.prototype.getRingAnimationDuration=function(){return this.event.getRingAnimationDuration()};Event.prototype.getRingRadius=function(){return this.event.getRingRadius()};Event.prototype.draw=function(width,height){let no_label=!1;let size=this.getSize();const self=this;Math.seedrandom(this.event.getPayload());let x=Math.random()*(width-size)+size;let y=Math.random()*(height-size)+size;let circle_group=this.svg.append('g').classed(this.getClassName(),!0).attr('transform','translate('+x+', '+y+')').style('opacity',1);let ring=circle_group.append('circle');ring.attr({r:size,stroke:'none'});ring.transition().attr('r',size+this.getRingRadius()).style('opacity',0).ease(Math.sqrt).duration(this.getRingAnimationDuration()).remove();let circle_container=circle_group.append('a');circle_container.attr('xlink:href',this.event.getUrl());circle_container.attr('target','_blank');circle_container.attr('fill',textColor);let circle=circle_container.append('circle');circle.classed(this.getClassName(),!0);circle.attr('r',size).transition().duration(maxLife).style('opacity',0).remove();circle_container.on('mouseover',function(){circle_container.append('text').text(self.getText()).classed('label',!0).attr('text-anchor','middle').attr('font-size','0.8em').transition().delay(1000).style('opacity',0).duration(2000).each(function(){no_label=!0}).remove()});circle_container.append('text').text(this.getText()).classed('article-label',!0).attr('text-anchor','middle').attr('font-size','0.8em').transition().delay(2000).style('opacity',0).duration(5000).each(function(){no_label=!0}).remove()};function Gui(){this.svg=exports.d3.select('#area').append('svg');exports.addEventListener('resize',this.resize.bind(this));this.setupVolumeSlider();this.resize()} 9 | Gui.prototype.setupVolumeSlider=function(){$('#volumeSlider').slider({max:100,min:0,value:localStorage.getItem('volume')*100,slide:function(event,ui){exports.Howler.volume(ui.value/100.0);localStorage.setItem('volume',ui.value/100.0)},change:function(event,ui){exports.Howler.volume(ui.value/100.0);localStorage.setItem('volume',ui.value/100.0)}})};Gui.prototype.getWidth=function(){return exports.innerWidth};Gui.prototype.getHeight=function(){return exports.innerHeight};Gui.prototype.resize=function(){this.svg.attr('width',this.getWidth());this.svg.attr('height',this.getHeight())};Gui.prototype.drawEvent=function(event){if(document.hidden){return} 10 | new Event(event,this.svg).draw(this.getWidth(),this.getHeight());let $area=$('#area');if($area.find('svg g').length>50){$area.find('svg g:lt(10)').remove()}};return Gui}());function ConnectedUsersMessage(response){this.count=response.connectedUsers} 11 | ConnectedUsersMessage.prototype.getCount=function(){return this.count};function EventMessage(event){this.event=event} 12 | EventMessage.prototype.getId=function(){return this.event.id};EventMessage.prototype.getType=function(){return this.event.type};EventMessage.prototype.getUrl=function(){return this.event.information.url};EventMessage.prototype.getPayload=function(){return this.event.information.payload};EventMessage.prototype.getMessage=function(){return this.event.information.message};EventMessage.prototype.getRingAnimationDuration=function(){return this.event.ring.animationDuration};EventMessage.prototype.getRingRadius=function(){return this.event.ring.radius};EventMessage.prototype.getSoundType=function(){return this.event.sound.type};EventMessage.prototype.getSoundSize=function(){return this.event.sound.size};function EventMessageCollection(response){this.events=[];for(let i=0;i1000){this.queue=this.queue.slice(0,1000)}};EventQueue.prototype.exists=function(event){for(let i=0;i