Completed
Push — master ( 027b80...dfb84a )
by Pieter
02:53
created

AudioPlayer.initializeEasterEggs   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 19
rs 9.4285
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 = 0.6;
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(0.7);
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({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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({
0 ignored issues
show
Bug introduced by
The variable Howl seems to be never declared. If this is a global, consider adding a /** global: Howl */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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.getMessage().length)) * scaleFactor, 3);
180
        };
181
182
        Event.prototype.getText = function() {
183
            switch(this.event.getType()){
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
184
                case 'PushEvent':
185
                    return this.event.getActorName() + ' pushed to ' + this.event.getRepositoryName();
186
                case 'PullRequestEvent':
187
                    return this.event.getActorName() + ' ' + this.event.getAction() + ' ' + ' a PR for ' + this.event.getRepositoryName();
188
                case 'IssuesEvent':
189
                    return this.event.getActorName() + ' ' + this.event.getAction() + ' an issue in ' + this.event.getRepositoryName();
190
                case 'IssueCommentEvent':
191
                    return this.event.getActorName() + ' commented in ' + this.event.getRepositoryName();
192
                case 'ForkEvent':
193
                    return this.event.getActorName() + ' forked ' + this.event.getRepositoryName();
194
                case 'CreateEvent':
195
                    return this.event.getActorName() + ' created ' + this.event.getRepositoryName();
196
                case 'WatchEvent':
197
                    return this.event.getActorName() + ' watched ' + this.event.getRepositoryName();
198
            }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
199
        };
200
201
        Event.prototype.getBackgroundColor = function() {
202
            switch(this.event.getType()){
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
203
                case 'PushEvent':
204
                    return '#22B65D';
205
                case 'PullRequestEvent':
206
                    return '#8F19BB';
207
                case 'IssuesEvent':
208
                    return '#ADD913';
209
                case 'IssueCommentEvent':
210
                    return '#FF4901';
211
                case 'ForkEvent':
212
                    return '#0184FF';
213
                case 'CreateEvent':
214
                    return '#00C0C0';
215
                case 'WatchEvent':
216
                    return '#E60062';
217
            }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
218
        };
219
220
        Event.prototype.getRingAnimationDuration = function() {
221
            if (this.event.getType() === 'PullRequestEvent') {
222
                return 10000;
223
            }
224
225
            return 3000;
226
        };
227
228
        Event.prototype.getRingRadius = function() {
229
            if (this.event.getType() === 'PullRequestEvent') {
230
                return 600;
231
            }
232
233
            return 80;
234
        };
235
236
        Event.prototype.draw = function(width, height) {
237
            let no_label = false;
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
238
            let size     = this.getSize();
239
240
            const self = this;
241
242
            //noinspection JSUnresolvedFunction
243
            Math.seedrandom(this.event.getMessage());
244
            let x = Math.random() * (width - size) + size;
245
            let y = Math.random() * (height - size) + size;
246
247
            let circle_group = this.svg.append('g')
248
                .attr('transform', 'translate(' + x + ', ' + y + ')')
249
                .attr('fill', this.getBackgroundColor())
250
                .style('opacity', 1);
251
252
            let ring = circle_group.append('circle');
253
            ring.attr({r: size, stroke: 'none'});
254
            ring.transition()
255
                .attr('r', size + this.getRingRadius())
256
                .style('opacity', 0)
257
                .ease(Math.sqrt)
258
                .duration(this.getRingAnimationDuration())
259
                .remove();
260
261
            let circle_container = circle_group.append('a');
262
            circle_container.attr('xlink:href', this.event.getUrl());
263
            circle_container.attr('target', '_blank');
264
            circle_container.attr('fill', textColor);
265
266
            let circle = circle_container.append('circle');
267
            circle.classed(this.event.getType(), true);
268
            circle.attr('r', size)
269
                .attr('fill', this.getBackgroundColor())
270
                .transition()
271
                .duration(maxLife)
272
                .style('opacity', 0)
273
                .remove();
274
275
            circle_container.on('mouseover', function() {
276
                circle_container.append('text')
277
                    .text(self.getText())
278
                    .classed('label', true)
279
                    .attr('text-anchor', 'middle')
280
                    .attr('font-size', '0.8em')
281
                    .transition()
282
                    .delay(1000)
283
                    .style('opacity', 0)
284
                    .duration(2000)
285
                    .each(function() { no_label = true; })
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
286
                    .remove();
287
            });
288
289
            circle_container.append('text')
290
                .text(this.getText())
291
                .classed('article-label', true)
292
                .attr('text-anchor', 'middle')
293
                .attr('font-size', '0.8em')
294
                .transition()
295
                .delay(2000)
296
                .style('opacity', 0)
297
                .duration(5000)
298
                .each(function() { no_label = true; })
0 ignored issues
show
Unused Code introduced by
The variable no_label seems to be never used. Consider removing it.
Loading history...
299
                .remove();
300
        };
301
302
        function Gui() {
303
            //noinspection JSUnresolvedVariable
304
            this.svg = exports.d3.select('#area').append('svg');
305
306
            exports.addEventListener('resize', this.resize.bind(this));
307
308
            this.setupVolumeSlider();
309
            this.resize();
310
        }
311
312
        Gui.prototype.setupVolumeSlider = function() {
313
            //noinspection JSUnresolvedFunction
314
            $('#volumeSlider').slider({
315
                max: 100,
316
                min: 0,
317
                value: volume * 100,
0 ignored issues
show
Bug introduced by
The variable volume seems to be never declared. If this is a global, consider adding a /** global: volume */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
318
                slide: function (event, ui) {
319
                    //noinspection JSUnresolvedVariable
320
                    exports.Howler.volume(ui.value/100.0);
321
                },
322
                change: function (event, ui) {
323
                    //noinspection JSUnresolvedVariable
324
                    exports.Howler.volume(ui.value/100.0);
325
                }
326
            });
327
        };
328
329
        Gui.prototype.getWidth = function() {
330
            return exports.innerWidth;
331
        };
332
333
        Gui.prototype.getHeight = function() {
334
            return exports.innerHeight - $('header').height();
335
        };
336
337
        Gui.prototype.resize = function() {
338
            this.svg.attr('width', this.getWidth());
339
            this.svg.attr('height', this.getHeight());
340
        };
341
342
        Gui.prototype.drawEvent = function(event) {
343
            if (document.hidden) {
344
                return;
345
            }
346
347
            new Event(event, this.svg).draw(this.getWidth(), this.getHeight());
348
349
            // Remove HTML of decayed events
350
            // Keep it less than 50
351
            let $area = $('#area');
352
            if($area.find('svg g').length > 50){
353
                $area.find('svg g:lt(10)').remove();
354
            }
355
        };
356
357
        return Gui;
358
    }());
359
360
    /**
361
     * ConnectedUsersMessage
362
     */
363
    function ConnectedUsersMessage(response) {
364
        //noinspection JSUnresolvedVariable
365
        this.count = response.connectedUsers;
366
    }
367
368
    ConnectedUsersMessage.prototype.getCount = function() {
369
        return this.count;
370
    };
371
372
    /**
373
     * EventMessage
374
     */
375
    function EventMessage(event) {
376
        this.event = event;
377
    }
378
379
    EventMessage.prototype.getId = function() {
380
        //noinspection JSUnresolvedVariable
381
        return this.event.id;
382
    };
383
384
    EventMessage.prototype.getType = function() {
385
        //noinspection JSUnresolvedVariable
386
        return this.event.type;
387
    };
388
389
    EventMessage.prototype.getAction = function() {
390
        //noinspection JSUnresolvedVariable
391
        return this.event.action;
392
    };
393
394
    EventMessage.prototype.getRepositoryName = function() {
395
        //noinspection JSUnresolvedVariable
396
        return this.event.repoName;
397
    };
398
399
    EventMessage.prototype.getActorName = function() {
400
        //noinspection JSUnresolvedVariable
401
        return this.event.actorName;
402
    };
403
404
    EventMessage.prototype.getUrl = function() {
405
        //noinspection JSUnresolvedVariable
406
        return this.event.eventUrl;
407
    };
408
409
    EventMessage.prototype.getMessage = function() {
410
        //noinspection JSUnresolvedVariable
411
        return this.event.message;
412
    };
413
414
    /**
415
     * EventMessageCollection
416
     */
417
    function EventMessageCollection(response) {
418
        this.events = [];
419
420
        for (let i = 0; i < response.length; i++) {
421
            this.events.push(new EventMessage(response[i]));
422
        }
423
    }
424
425
    EventMessageCollection.prototype.forEach = function(callback) {
426
        for (let i = 0; i < this.events.length; i++) {
427
            callback(this.events[i]);
428
        }
429
    };
430
431
    /**
432
     * EventMessagesFactory
433
     */
434
    function EventMessagesFactory () {
435
    }
436
437
    EventMessagesFactory.prototype.build = function(response) {
438
        const parsedResponse = JSON.parse(response.data);
439
440
        if (parsedResponse.hasOwnProperty('connectedUsers')) {
441
            return new ConnectedUsersMessage(parsedResponse);
442
        }
443
444
        return new EventMessageCollection(parsedResponse);
445
    };
446
447
    /**
448
     * EventQueue
449
     */
450
    function EventQueue() {
451
        this.queue = [];
452
    }
453
454
    EventQueue.prototype.append = function(eventMessages) {
455
        eventMessages.forEach(function(event) {
456
            if (this.exists(event)) {
457
                return;
458
            }
459
460
            this.queue.push(event);
461
        }.bind(this));
462
463
        if (this.queue.length > 1000) {
464
            this.queue = this.queue.slice(0, 1000);
465
        }
466
    };
467
468
    EventQueue.prototype.exists = function(event) {
469
        for (let i = 0; i < this.queue.length; i++) {
470
            if (event.getId() === this.queue[i].getId()) {
471
                return true;
472
            }
473
        }
474
475
        return false;
476
    };
477
478
    EventQueue.prototype.get = function() {
479
        return this.queue.shift();
480
    };
481
482
    EventQueue.prototype.count = function() {
483
        return this.queue.length;
484
    };
485
486
    /**
487
     * Connection
488
     */
489
    function Connection(eventMessageFactory) {
490
        this.eventMessageFactory = eventMessageFactory;
491
492
        this.connection = null;
493
        this.handlers   = [];
494
    }
495
496
    Connection.prototype.start = function() {
497
        let protocol = 'ws://';
498
499
        if (exports.location.protocol === "https:") {
500
            protocol = 'wss://';
501
        }
502
503
        try {
504
            this.connection = new WebSocket(protocol + exports.location.host + '/ws');
0 ignored issues
show
Bug introduced by
The variable WebSocket seems to be never declared. If this is a global, consider adding a /** global: WebSocket */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
505
506
            this.connection.addEventListener('message', this.handleMessage.bind(this));
507
            this.connection.addEventListener('open', this.handleOpen.bind(this));
508
            this.connection.addEventListener('close', this.reconnect.bind(this));
509
            this.connection.addEventListener('error', this.reconnect.bind(this));
510
        } catch(e) {
511
            this.connection = null;
512
        }
513
    };
514
515
    Connection.prototype.registerHandler = function(handler) {
516
        this.handlers.push(handler);
517
    };
518
519
    Connection.prototype.handleMessage = function(response) {
520
        const message = this.eventMessageFactory.build(response);
521
522
        for (let i = 0; i < this.handlers.length; i++) {
523
            this.handlers[i](message);
524
        }
525
    };
526
527
    Connection.prototype.handleOpen = function() {
528
        const elements = document.querySelectorAll('.events-remaining-text, .events-remaining-value, .online-users-div');
529
530
        for (let i = 0; i < elements.length; i++) {
531
            elements[i].style.visibility = 'visible';
532
        }
533
    };
534
535
    Connection.prototype.reconnect = function() {
536
        // prevent piling up reconnect
537
        if (this.connection.readyState === 0 || this.connection.readyState === 1) {
538
            return;
539
        }
540
541
        setTimeout(function() {
542
            this.start();
543
        }.bind(this), 5000);
544
    };
545
546
    /**
547
     * Application
548
     */
549
    function Application() {
550
        this.queue = new EventQueue();
551
        this.audio = new AudioPlayer();
552
        this.gui   = new Gui();
553
    }
554
555
    Application.prototype.run = function() {
556
        const connection = new Connection(new EventMessagesFactory());
557
558
        connection.registerHandler(this.process.bind(this));
559
560
        connection.start();
561
562
        this.loop();
563
    };
564
565
    Application.prototype.process = function(message) {
566
        if (message instanceof ConnectedUsersMessage) {
567
            document.getElementsByClassName('online-users-count')[0].textContent = message.getCount();
568
569
            return;
570
        }
571
572
        this.queue.append(message);
573
    };
574
575
    Application.prototype.loop = function() {
576
        setTimeout(function() {
577
            this.loop();
578
579
            if (!this.queue.count()) {
580
                return;
581
            }
582
583
            this.processEvent(this.queue.get());
584
585
            document.getElementsByClassName('events-remaining-value')[0].textContent = this.queue.count();
586
        }.bind(this), Math.floor(Math.random() * 1000) + 500);
587
    };
588
589
    Application.prototype.processEvent = function(event) {
590
        if (!event.getMessage()) {
591
            return;
592
        }
593
594
        if (event.getRepositoryName() === 'ekinhbayar/gitamp') {
595
            if (event.getType() === 'IssuesEvent' || event.getType() === 'IssueCommentEvent') {
596
                this.audio.playClavEgg();
597
            } else if(event.getType() === 'PushEvent') {
598
                this.audio.playCelestaEgg();
599
            }else{
600
                this.audio.playSwellEgg();
601
            }
602
        }
603
604
        if (event.getType() === 'IssuesEvent' || event.getType() === 'IssueCommentEvent') {
605
            this.audio.playClav(event.getMessage().length * 1.1);
606
        } else if(event.getType() === 'PushEvent') {
607
            this.audio.playCelesta(event.getMessage().length * 1.1);
608
        }else{
609
            this.audio.playSwell();
610
        }
611
612
        this.gui.drawEvent(event);
613
    };
614
615
    return Application;
616
}(window, jQuery));
617
618
$(function() {
619
    new GitAmp().run();
620
});
621