-
-
Notifications
You must be signed in to change notification settings - Fork 215
Expand file tree
/
Copy pathsrc_engineAudio.js.html
More file actions
681 lines (607 loc) · 49 KB
/
src_engineAudio.js.html
File metadata and controls
681 lines (607 loc) · 49 KB
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="./static/favicon.png"><meta name="description" content="LittleJS is a lightweight HTML5 game engine with fast WebGL rendering, physics, particles, sound, and more!"><meta name="keywords" content="javascript, game engine, html5, webgl, 2d, game development, indie games"><meta name="author" content="Frank Force"><meta property="og:title" content="LittleJS - The Tiny JavaScript Game Engine"><meta property="og:description" content="Lightweight HTML5 game engine with WebGL rendering, physics, and sound"><meta property="og:type" content="website"><title>Source: src/engineAudio.js</title><!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(baseURL=(baseURL="https://killedbyapixel.github.io/LittleJS/docs/").replace(/https?:\/\//i,"")).substr(baseURL.indexOf("/"))</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="dark"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">LittleJS - The Tiny JavaScript Game Engine That Can!</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Audio.Sound.html">Sound</a></div><div class="sidebar-section-children"><a href="Audio.SoundInstance.html">SoundInstance</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dDistanceJoint.html">Box2dDistanceJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dFrictionJoint.html">Box2dFrictionJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dGearJoint.html">Box2dGearJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dJoint.html">Box2dJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dKinematicObject.html">Box2dKinematicObject</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dMotorJoint.html">Box2dMotorJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dObject.html">Box2dObject</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPinJoint.html">Box2dPinJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPlugin.html">Box2dPlugin</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPrismaticJoint.html">Box2dPrismaticJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPulleyJoint.html">Box2dPulleyJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dRevoluteJoint.html">Box2dRevoluteJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dRopeJoint.html">Box2dRopeJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dStaticObject.html">Box2dStaticObject</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dTargetJoint.html">Box2dTargetJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dTileLayer.html">Box2dTileLayer</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dWeldJoint.html">Box2dWeldJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dWheelJoint.html">Box2dWheelJoint</a></div><div class="sidebar-section-children"><a href="Box2dRaycastResult.html">Box2dRaycastResult</a></div><div class="sidebar-section-children"><a href="Draw.FontImage.html">FontImage</a></div><div class="sidebar-section-children"><a href="Draw.TextureInfo.html">TextureInfo</a></div><div class="sidebar-section-children"><a href="Draw.TileInfo.html">TileInfo</a></div><div class="sidebar-section-children"><a href="Engine.Color.html">Color</a></div><div class="sidebar-section-children"><a href="Engine.EngineObject.html">EngineObject</a></div><div class="sidebar-section-children"><a href="Engine.RandomGenerator.html">RandomGenerator</a></div><div class="sidebar-section-children"><a href="Engine.Timer.html">Timer</a></div><div class="sidebar-section-children"><a href="Engine.Vector2.html">Vector2</a></div><div class="sidebar-section-children"><a href="Medals.Medal.html">Medal</a></div><div class="sidebar-section-children"><a href="Newgrounds.NewgroundsMedal.html">NewgroundsMedal</a></div><div class="sidebar-section-children"><a href="Newgrounds.NewgroundsPlugin.html">NewgroundsPlugin</a></div><div class="sidebar-section-children"><a href="Particles.Particle.html">Particle</a></div><div class="sidebar-section-children"><a href="Particles.ParticleEmitter.html">ParticleEmitter</a></div><div class="sidebar-section-children"><a href="PostProcess.PostProcessPlugin.html">PostProcessPlugin</a></div><div class="sidebar-section-children"><a href="TileLayers.CanvasLayer.html">CanvasLayer</a></div><div class="sidebar-section-children"><a href="TileLayers.TileCollisionLayer.html">TileCollisionLayer</a></div><div class="sidebar-section-children"><a href="TileLayers.TileLayer.html">TileLayer</a></div><div class="sidebar-section-children"><a href="TileLayers.TileLayerData.html">TileLayerData</a></div><div class="sidebar-section-children"><a href="UISystem.UIButton.html">UIButton</a></div><div class="sidebar-section-children"><a href="UISystem.UICheckbox.html">UICheckbox</a></div><div class="sidebar-section-children"><a href="UISystem.UIObject.html">UIObject</a></div><div class="sidebar-section-children"><a href="UISystem.UIScrollbar.html">UIScrollbar</a></div><div class="sidebar-section-children"><a href="UISystem.UISystemPlugin.html">UISystemPlugin</a></div><div class="sidebar-section-children"><a href="UISystem.UIText.html">UIText</a></div><div class="sidebar-section-children"><a href="UISystem.UITile.html">UITile</a></div><div class="sidebar-section-children"><a href="UISystem.UIVideo.html">UIVideo</a></div><div class="sidebar-section-children"><a href="ZzFXM.ZzFXMusic.html">ZzFXMusic</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-namespaces"><div>Namespaces</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Audio.html">Audio</a></div><div class="sidebar-section-children"><a href="Box2D.html">Box2D</a></div><div class="sidebar-section-children"><a href="Debug.html">Debug</a></div><div class="sidebar-section-children"><a href="Draw.html">Draw</a></div><div class="sidebar-section-children"><a href="DrawUtilities.html">DrawUtilities</a></div><div class="sidebar-section-children"><a href="Engine.html">Engine</a></div><div class="sidebar-section-children"><a href="Input.html">Input</a></div><div class="sidebar-section-children"><a href="Math.html">Math</a></div><div class="sidebar-section-children"><a href="Medals.html">Medals</a></div><div class="sidebar-section-children"><a href="Newgrounds.html">Newgrounds</a></div><div class="sidebar-section-children"><a href="Particles.html">Particles</a></div><div class="sidebar-section-children"><a href="PostProcess.html">PostProcess</a></div><div class="sidebar-section-children"><a href="Random.html">Random</a></div><div class="sidebar-section-children"><a href="Settings.html">Settings</a></div><div class="sidebar-section-children"><a href="TileLayers.html">TileLayers</a></div><div class="sidebar-section-children"><a href="UISystem.html">UISystem</a></div><div class="sidebar-section-children"><a href="Utilities.html">Utilities</a></div><div class="sidebar-section-children"><a href="WebGL.html">WebGL</a></div><div class="sidebar-section-children"><a href="ZzFXM.html">ZzFXM</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://github.com/KilledByAPixel/LittleJS" target="_blank">GitHub</a></div><div class="navbar-item"><a id="" href="https://killedbyapixel.github.io/LittleJS/examples/" target="_blank">Examples</a></div><div class="navbar-item"><a id="" href="https://github.com/KilledByAPixel/LittleJS/blob/main/FAQ.md" target="_blank">FAQ</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#light-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">src_engineAudio.js</h1></header><article><pre class="prettyprint source lang-js"><code>/**
* LittleJS Audio System
* - Play audio files (mp3, ogg, wave) and generate sounds with ZzFX
* - ZzFX sound generator integration: <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX</a>
* - Sound caching for fast playback and memory efficiency
* - Volume control with attenuation and stereo panning
* - 2D spatial audio based on camera position with distance-based falloff
* - Sound instance management (pause, resume, stop)
* - Speech synthesis for text-to-speech
* - Music playback with ZzFXM support
* - Web Audio API integration with master gain control
* @namespace Audio
*/
'use strict';
/** Audio context used by the engine
* @type {AudioContext}
* @memberof Audio */
let audioContext = new AudioContext;
/** Master gain node for all audio to pass through
* @type {GainNode}
* @memberof Audio */
let audioMasterGain;
/** Default sample rate used for sounds
* @default 44100
* @memberof Audio */
const audioDefaultSampleRate = 44100;
/** Check if the audio context is running and available for playback
* @return {boolean} - True if the audio context is running
* @memberof Audio */
function audioIsRunning()
{ return audioContext.state === 'running'; }
function audioInit()
{
if (!soundEnable || headlessMode) return;
audioMasterGain = audioContext.createGain();
audioMasterGain.connect(audioContext.destination);
audioMasterGain.gain.value = soundVolume; // set starting value
}
///////////////////////////////////////////////////////////////////////////////
/**
* Sound Object - Stores a sound for later
* - this can be used to load and play wave, mp3, and ogg files
* - it can also create sounds using the ZzFX sound generator
* - can attenuate and apply stereo panning to sounds
* - sound instance control with pause/resume capability
*
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
* @memberof Audio
* @example
* // load an audio asset file
* const sound_example = new Sound('sound.mp3');
*
* // create a zzfx sound
* const sound_example = new Sound([.5,.5]);
*
* // play a sound
* sound_example.play();
*/
class Sound
{
/**
* @callback SoundLoadCallback - Function called when sound is loaded
* @param {Sound} sound
* @memberof Audio
*/
/** Create a sound object and cache the audio for later use
* @param {string|Array} [asset] - Filename of audio file or zzfx array
* @param {number} [randomness] - How much to randomize frequency each time sound plays, for zzfx sounds the zzfx default is used if undefined
* @param {number} [range=soundDefaultRange] - World space max range of sound
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
*/
constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
{
if (!soundEnable || headlessMode) return;
ASSERT(!asset || isArray(asset) || isString(asset), 'asset must be a file name or zzfx array');
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
ASSERT(isNumber(range), 'range must be a number');
ASSERT(isNumber(taper), 'taper must be a number');
/** @property {number} - World space max range of sound */
this.range = range;
/** @property {number} - At what percentage of range should it start tapering */
this.taper = taper;
/** @property {number} - How much to randomize frequency each time sound plays */
this.randomness = randomness ?? 0;
/** @property {number} - Sample rate for this sound */
this.sampleRate = audioDefaultSampleRate;
/** @property {number} - Percentage of this sound currently loaded */
this.loadedPercent = 0;
/** @property {SoundLoadCallback} - function to call when sound is loaded */
this.onloadCallback = onloadCallback;
if (Array.isArray(asset))
{
// generate zzfx sound
const zzfxSound = asset;
// remove randomness so it can be applied on playback
const defaultRandomness = randomness ?? .05;
const randomnessIndex = 1;
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
zzfxSound[randomnessIndex] = 0;
// generate the zzfx samples
this.sampleChannels = [zzfxG(...zzfxSound)];
this.loadedPercent = 1;
onloadCallback?.(this);
}
else if (typeof asset === 'string')
{
// load the audio file
const filename = asset;
this.loadSound(filename);
}
}
/** Play the sound
* Sounds may not play until a user interaction occurs
* @param {Vector2} [pos] - World space position to play the sound if any
* @param {number} [volume] - How much to scale volume by
* @param {number} [pitch] - How much to scale pitch by
* @param {number} [randomnessScale] - How much to scale pitch randomness
* @param {boolean} [loop] - Should the sound loop?
* @param {boolean} [paused] - Should the sound start paused
* @return {SoundInstance} - The audio source node
*/
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
{
ASSERT(!pos || isVector2(pos), 'pos must be a vec2');
ASSERT(isNumber(volume), 'volume must be a number');
ASSERT(isNumber(pitch), 'pitch must be a number');
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
if (!soundEnable || headlessMode) return;
if (!this.sampleChannels) return;
let pan;
if (pos)
{
const range = this.range;
if (range)
{
// apply range based fade
const lengthSquared = cameraPos.distanceSquared(pos);
if (lengthSquared > range*range)
return; // out of range
// attenuate volume by distance
volume *= percent(lengthSquared**.5, range, range*this.taper);
}
// get pan from screen space coords
pan = worldToScreen(pos).x * 2/mainCanvas.width - 1;
}
// Create and return sound instance
const rate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
return new SoundInstance(this, volume, rate, pan, loop, paused);
}
/** Play a music track that loops by default
* @param {number} [volume] - Volume to play the music at
* @param {boolean} [loop] - Should the music loop?
* @param {boolean} [paused] - Should the music start paused
* @return {SoundInstance} - The sound instance
*/
playMusic(volume=1, loop=true, paused=false)
{ return this.play(undefined, volume, 1, 0, loop, paused); }
/** Play the sound as a musical note with a semitone offset
* This can be used to play music with chromatic scales
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
* @param {Vector2} [pos] - World space position to play the sound if any
* @param {number} [volume=1] - How much to scale volume by
* @return {SoundInstance} - The sound instance
*/
playNote(semitoneOffset=0, pos, volume)
{
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
const pitch = getNoteFrequency(semitoneOffset, 1);
return this.play(pos, volume, pitch, 0);
}
/** Get how long this sound is in seconds
* @return {number} - How long the sound is in seconds (undefined if loading)
*/
getDuration()
{ return this.sampleChannels?.[0]?.length / this.sampleRate || 0; }
/** Check if sound is loaded, for sounds fetched from a url
* @return {boolean} - True if sound is loaded and ready to play
*/
isLoaded() { return this.loadedPercent === 1; }
/** Loads a sound from a URL and decodes it into sample data.
* @param {string} filename
* @return {Promise} */
async loadSound(filename)
{
const response = await fetch(filename);
if (!response.ok)
throw new Error(`Failed to load sound from ${filename}: ${response.status} ${response.statusText}`);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// convert audio buffer to sample channels across multiple frames
const channelCount = audioBuffer.numberOfChannels;
const samplesPerFrame = 1e5;
const sampleChannels = [];
for (let channel = 0; channel < channelCount; channel++)
{
const channelData = audioBuffer.getChannelData(channel);
const channelLength = channelData.length;
sampleChannels[channel] = new Array(channelLength);
let sampleIndex = 0;
while (sampleIndex < channelLength)
{
// yield to next frame
await new Promise(resolve => setTimeout(resolve, 0));
// copy chunk of samples
const endIndex = min(sampleIndex + samplesPerFrame, channelLength);
for (; sampleIndex < endIndex; sampleIndex++)
sampleChannels[channel][sampleIndex] = channelData[sampleIndex];
// update loaded percent
const samplesTotal = channelCount * channelLength;
const samplesProcessed = channel * channelLength + sampleIndex;
this.loadedPercent = samplesProcessed / samplesTotal;
}
}
// setup the sound to be played
this.sampleRate = audioBuffer.sampleRate;
this.sampleChannels = sampleChannels;
this.loadedPercent = 1;
this.onloadCallback?.(this);
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
* Represents a single playing instance of a sound with pause/resume capabilities
* @memberof Audio
* @example
* // Play a sound and get an instance for control
* const jumpSound = new Sound([.5,.5,220]);
* const instance = jumpSound.play();
*
* // Control the individual instance
* instance.setVolume(.5);
* instance.pause();
* instance.resume();
* instance.stop();
*/
class SoundInstance
{
/** Create a sound instance
* @param {Sound} sound - The sound object
* @param {number} [volume] - How much to scale volume by
* @param {number} [rate] - The playback rate to use
* @param {number} [pan] - How much to apply stereo panning
* @param {boolean} [loop] - Should the sound loop?
* @param {boolean} [paused] - Should the sound start paused? */
constructor(sound, volume=1, rate=1, pan=0, loop=false, paused=false)
{
ASSERT(sound instanceof Sound, 'SoundInstance requires a valid Sound object');
ASSERT(volume >= 0, 'Sound volume must be positive or zero');
ASSERT(rate >= 0, 'Sound rate must be positive or zero');
ASSERT(isNumber(pan), 'Sound pan must be a number');
/** @property {Sound} - The sound object */
this.sound = sound;
/** @property {number} - How much to scale volume by */
this.volume = volume;
/** @property {number} - The playback rate to use */
this.rate = rate;
/** @property {number} - How much to apply stereo panning */
this.pan = pan;
/** @property {boolean} - Should the sound loop */
this.loop = loop;
/** @property {number} - Timestamp for audio context when paused */
this.pausedTime = 0;
/** @property {number} - Timestamp for audio context when started */
this.startTime = undefined;
/** @property {GainNode} - Gain node for the sound */
this.gainNode = undefined;
/** @property {AudioBufferSourceNode} - Source node of the audio */
this.source = undefined;
// setup end callback and start sound
this.onendedCallback = (source)=>
{
if (source === this.source)
this.source = undefined;
};
if (!paused)
this.start();
}
/** Start playing the sound instance from the offset time
* @param {number} [offset] - Offset in seconds to start playback from
*/
start(offset=0)
{
ASSERT(offset >= 0, 'Sound start offset must be positive or zero');
if (this.isPlaying())
this.stop();
this.gainNode = audioContext.createGain();
this.source = playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
if (this.source)
{
this.startTime = audioContext.currentTime - offset;
this.pausedTime = undefined;
}
else
{
this.startTime = undefined;
this.pausedTime = 0;
}
}
/** Set the volume of this sound instance
* @param {number} volume */
setVolume(volume)
{
ASSERT(volume >= 0, 'Sound volume must be positive or zero');
this.volume = volume;
if (this.gainNode)
this.gainNode.gain.value = volume;
}
/** Stop this sound instance and reset position to the start */
stop(fadeTime=0)
{
ASSERT(fadeTime >= 0, 'Sound fade time must be positive or zero');
if (this.isPlaying())
{
if (fadeTime)
{
// ramp off gain
const startFade = audioContext.currentTime;
const endFade = startFade + fadeTime;
this.gainNode.gain.linearRampToValueAtTime(1, startFade);
this.gainNode.gain.linearRampToValueAtTime(0, endFade);
this.source.stop(endFade);
}
else
this.source.stop();
}
this.pausedTime = 0;
this.source = undefined;
this.startTime = undefined;
}
/** Pause this sound instance */
pause()
{
if (this.isPaused()) return;
// save current time and stop sound
this.pausedTime = this.getCurrentTime();
this.source.stop();
this.source = undefined;
this.startTime = undefined;
}
/** Resume this sound instance */
resume()
{
if (!this.isPaused()) return;
// restart sound from paused time
this.start(this.pausedTime);
}
/** Check if this instance is currently playing
* @return {boolean} - True if playing
*/
isPlaying() { return !!this.source; }
/** Check if this instance is paused or stopped (not currently playing)
* @return {boolean} - True if not playing
*/
isPaused() { return !this.isPlaying(); }
/** Get the current playback time in seconds
* @return {number} - Current playback time
*/
getCurrentTime()
{
const deltaTime = mod(audioContext.currentTime - this.startTime,
this.getDuration());
return this.isPlaying() ? deltaTime : this.pausedTime;
}
/** Get the total duration of this sound
* @return {number} - Total duration in seconds
*/
getDuration() { return this.rate ? this.sound.getDuration() / this.rate : 0; }
/** Get source of this sound instance
* @return {AudioBufferSourceNode}
*/
getSource() { return this.source; }
}
///////////////////////////////////////////////////////////////////////////////
/** Speak text with passed in settings
* @param {string} text - The text to speak
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
* @param {number} [volume] - How much to scale volume by
* @param {number} [rate] - How quickly to speak
* @param {number} [pitch] - How much to change the pitch by
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
* @memberof Audio */
function speak(text, language='', volume=1, rate=1, pitch=1)
{
if (!soundEnable || headlessMode) return;
if (!speechSynthesis) return;
// common languages (not supported by all browsers)
// en - english, it - italian, fr - french, de - german, es - spanish
// ja - japanese, ru - russian, zh - chinese, hi - hindi, ko - korean
// build utterance and speak
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = language;
utterance.volume = 2*volume*soundVolume;
utterance.rate = rate;
utterance.pitch = pitch;
speechSynthesis.speak(utterance);
return utterance;
}
/** Stop all queued speech
* @memberof Audio */
function speakStop() {speechSynthesis?.cancel();}
/** Get frequency of a note on a musical scale
* @param {number} semitoneOffset - How many semitones away from the root note
* @param {number} [rootFrequency=220] - Frequency at semitone offset 0
* @return {number} - The frequency of the note
* @memberof Audio */
function getNoteFrequency(semitoneOffset, rootFrequency=220)
{ return rootFrequency * 2**(semitoneOffset/12); }
///////////////////////////////////////////////////////////////////////////////
/**
* @callback AudioEndedCallback - Function called when a sound ends
* @param {AudioBufferSourceNode} source
* @memberof Audio
*/
/** Play cached audio samples with given settings
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
* @param {number} [volume] - How much to scale volume by
* @param {number} [rate] - The playback rate to use
* @param {number} [pan] - How much to apply stereo panning
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
* @param {number} [sampleRate=44100] - Sample rate for the sound
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
* @param {number} [offset] - Offset in seconds to start playback from
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
* @memberof Audio */
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
{
if (!soundEnable || headlessMode) return;
if (!audioIsRunning())
{
// fix stalled audio, this sound won't be able to play
audioContext.resume();
return;
}
// create buffer and source
const channelCount = sampleChannels.length;
const sampleLength = sampleChannels[0].length;
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
const source = audioContext.createBufferSource();
// copy samples to buffer and setup source
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
source.buffer = buffer;
source.playbackRate.value = rate;
source.loop = loop;
// create and connect gain node
gainNode = gainNode || audioContext.createGain();
gainNode.gain.value = volume;
gainNode.connect(audioMasterGain);
// connect source to stereo panner and gain
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
source.connect(pannerNode).connect(gainNode);
// callback when the sound ends
if (onended)
source.addEventListener('ended', ()=> onended(source));
// play and return sound
const startOffset = offset * rate;
source.start(0, startOffset);
return source;
}
///////////////////////////////////////////////////////////////////////////////
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
/** Generate and play a ZzFX sound
*
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
* @return {AudioBufferSourceNode} - The audio node of the sound played
* @memberof Audio */
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
/** Generate samples for a ZzFX sound
* @param {number} [volume] - Volume scale (percent)
* @param {number} [randomness] - How much to randomize frequency (percent Hz)
* @param {number} [frequency] - Frequency of sound (Hz)
* @param {number} [attack] - Attack time, how fast sound starts (seconds)
* @param {number} [sustain] - Sustain time, how long sound holds (seconds)
* @param {number} [release] - Release time, how fast sound fades out (seconds)
* @param {number} [shape] - Shape of the sound wave
* @param {number} [shapeCurve] - Squareness of wave (0=square, 1=normal, 2=pointy)
* @param {number} [slide] - How much to slide frequency (kHz/s)
* @param {number} [deltaSlide] - How much to change slide (kHz/s/s)
* @param {number} [pitchJump] - Frequency of pitch jump (Hz)
* @param {number} [pitchJumpTime] - Time of pitch jump (seconds)
* @param {number} [repeatTime] - Resets some parameters periodically (seconds)
* @param {number} [noise] - How much random noise to add (percent)
* @param {number} [modulation] - Frequency of modulation wave, negative flips phase (Hz)
* @param {number} [bitCrush] - Resamples at a lower frequency in (samples*100)
* @param {number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
* @param {number} [sustainVolume] - Volume level for sustain (percent)
* @param {number} [decay] - Decay time, how long to reach sustain after attack (seconds)
* @param {number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
* @param {number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
* @return {Array} - Array of audio samples
* @memberof Audio */
function zzfxG
(
volume = 1,
randomness = .05,
frequency = 220,
attack = 0,
sustain = 0,
release = .1,
shape = 0,
shapeCurve = 1,
slide = 0,
deltaSlide = 0,
pitchJump = 0,
pitchJumpTime = 0,
repeatTime = 0,
noise = 0,
modulation = 0,
bitCrush = 0,
delay = 0,
sustainVolume = 1,
decay = 0,
tremolo = 0,
filter = 0
)
{
// init parameters
let sampleRate = audioDefaultSampleRate,
PI2 = PI*2,
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
startFrequency = frequency *=
(1 + rand(randomness,-randomness)) * PI2 / sampleRate,
modOffset = 0, // modulation offset
repeat = 0, // repeat offset
crush = 0, // bit crush offset
jump = 1, // pitch jump timer
length, // sample length
b = [], // sample buffer
t = 0, // sample time
i = 0, // sample index
s = 0, // sample value
f, // wave frequency
// biquad LP/HP filter
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
cosw = cos(w), alpha = sin(w) / 2 / quality,
a0 = 1 + alpha, a1 = -2*cosw / a0, a2 = (1 - alpha) / a0,
b0 = (1 + sign(filter) * cosw) / 2 / a0,
b1 = -(sign(filter) + cosw) / a0, b2 = b0,
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
// scale by sample rate
const minAttack = 9; // prevent pop if attack is 0
attack = attack * sampleRate || minAttack;
decay *= sampleRate;
sustain *= sampleRate;
release *= sampleRate;
delay *= sampleRate;
deltaSlide *= 500 * PI2 / sampleRate**3;
modulation *= PI2 / sampleRate;
pitchJump *= PI2 / sampleRate;
pitchJumpTime *= sampleRate;
repeatTime = repeatTime * sampleRate | 0;
// generate waveform
for (length = attack + decay + sustain + release + delay | 0;
i < length; b[i++] = s * volume) // sample
{
if (!(++crush%(bitCrush*100|0))) // bit crush
{
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
sin(t**3) : // 4 noise
max(min(tan(t),1),-1): // 3 tan
1-(2*t/PI2%2+2)%2: // 2 saw
1-4*abs(round(t/PI2)-t/PI2): // 1 triangle
sin(t); // 0 sin
s = (repeatTime ?
1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
: 1) *
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
(i < attack ? i/attack : // attack
i < attack + decay ? // decay
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
i < attack + decay + sustain ? // sustain
sustainVolume : // sustain volume
i < length - delay ? // release
(length - i - delay)/release * // release falloff
sustainVolume : // release volume
0); // post release
s = delay ? s/2 + (delay > i ? 0 : // delay
(i<length-delay? 1 : (length-i)/delay) * // release delay
b[i-delay|0]/2/volume) : s; // sample delay
if (filter) // apply filter
s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
}
f = (frequency += slide += deltaSlide) *// frequency
cos(modulation*modOffset++); // modulation
t += f + f*noise*sin(i**5); // noise
if (jump && ++jump > pitchJumpTime) // pitch jump
{
frequency += pitchJump; // apply pitch jump
startFrequency += pitchJump; // also apply to start
jump = 0; // stop pitch jump time
}
if (repeatTime && !(++repeat % repeatTime)) // repeat
{
frequency = startFrequency; // reset frequency
slide = startSlide; // reset slide
jump ||= 1; // reset pitch jump time
}
}
return b; // return sample buffer
}</code></pre></article></section><footer class="footer" id="PeOAagUepe"><div class="wrapper"><a href="https://github.com/KilledByAPixel/LittleJS">LittleJS - MIT License - Copyright 2021 Frank Force</a></div></footer></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">LittleJS - The Tiny JavaScript Game Engine That Can!</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://github.com/KilledByAPixel/LittleJS" target="_blank">GitHub</a></div><div class="navbar-item"><a id="" href="https://killedbyapixel.github.io/LittleJS/examples/" target="_blank">Examples</a></div><div class="navbar-item"><a id="" href="https://github.com/KilledByAPixel/LittleJS/blob/main/FAQ.md" target="_blank">FAQ</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Audio.Sound.html">Sound</a></div><div class="sidebar-section-children"><a href="Audio.SoundInstance.html">SoundInstance</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dDistanceJoint.html">Box2dDistanceJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dFrictionJoint.html">Box2dFrictionJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dGearJoint.html">Box2dGearJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dJoint.html">Box2dJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dKinematicObject.html">Box2dKinematicObject</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dMotorJoint.html">Box2dMotorJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dObject.html">Box2dObject</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPinJoint.html">Box2dPinJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPlugin.html">Box2dPlugin</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPrismaticJoint.html">Box2dPrismaticJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dPulleyJoint.html">Box2dPulleyJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dRevoluteJoint.html">Box2dRevoluteJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dRopeJoint.html">Box2dRopeJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dStaticObject.html">Box2dStaticObject</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dTargetJoint.html">Box2dTargetJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dTileLayer.html">Box2dTileLayer</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dWeldJoint.html">Box2dWeldJoint</a></div><div class="sidebar-section-children"><a href="Box2D.Box2dWheelJoint.html">Box2dWheelJoint</a></div><div class="sidebar-section-children"><a href="Box2dRaycastResult.html">Box2dRaycastResult</a></div><div class="sidebar-section-children"><a href="Draw.FontImage.html">FontImage</a></div><div class="sidebar-section-children"><a href="Draw.TextureInfo.html">TextureInfo</a></div><div class="sidebar-section-children"><a href="Draw.TileInfo.html">TileInfo</a></div><div class="sidebar-section-children"><a href="Engine.Color.html">Color</a></div><div class="sidebar-section-children"><a href="Engine.EngineObject.html">EngineObject</a></div><div class="sidebar-section-children"><a href="Engine.RandomGenerator.html">RandomGenerator</a></div><div class="sidebar-section-children"><a href="Engine.Timer.html">Timer</a></div><div class="sidebar-section-children"><a href="Engine.Vector2.html">Vector2</a></div><div class="sidebar-section-children"><a href="Medals.Medal.html">Medal</a></div><div class="sidebar-section-children"><a href="Newgrounds.NewgroundsMedal.html">NewgroundsMedal</a></div><div class="sidebar-section-children"><a href="Newgrounds.NewgroundsPlugin.html">NewgroundsPlugin</a></div><div class="sidebar-section-children"><a href="Particles.Particle.html">Particle</a></div><div class="sidebar-section-children"><a href="Particles.ParticleEmitter.html">ParticleEmitter</a></div><div class="sidebar-section-children"><a href="PostProcess.PostProcessPlugin.html">PostProcessPlugin</a></div><div class="sidebar-section-children"><a href="TileLayers.CanvasLayer.html">CanvasLayer</a></div><div class="sidebar-section-children"><a href="TileLayers.TileCollisionLayer.html">TileCollisionLayer</a></div><div class="sidebar-section-children"><a href="TileLayers.TileLayer.html">TileLayer</a></div><div class="sidebar-section-children"><a href="TileLayers.TileLayerData.html">TileLayerData</a></div><div class="sidebar-section-children"><a href="UISystem.UIButton.html">UIButton</a></div><div class="sidebar-section-children"><a href="UISystem.UICheckbox.html">UICheckbox</a></div><div class="sidebar-section-children"><a href="UISystem.UIObject.html">UIObject</a></div><div class="sidebar-section-children"><a href="UISystem.UIScrollbar.html">UIScrollbar</a></div><div class="sidebar-section-children"><a href="UISystem.UISystemPlugin.html">UISystemPlugin</a></div><div class="sidebar-section-children"><a href="UISystem.UIText.html">UIText</a></div><div class="sidebar-section-children"><a href="UISystem.UITile.html">UITile</a></div><div class="sidebar-section-children"><a href="UISystem.UIVideo.html">UIVideo</a></div><div class="sidebar-section-children"><a href="ZzFXM.ZzFXMusic.html">ZzFXMusic</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-namespaces"><div>Namespaces</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Audio.html">Audio</a></div><div class="sidebar-section-children"><a href="Box2D.html">Box2D</a></div><div class="sidebar-section-children"><a href="Debug.html">Debug</a></div><div class="sidebar-section-children"><a href="Draw.html">Draw</a></div><div class="sidebar-section-children"><a href="DrawUtilities.html">DrawUtilities</a></div><div class="sidebar-section-children"><a href="Engine.html">Engine</a></div><div class="sidebar-section-children"><a href="Input.html">Input</a></div><div class="sidebar-section-children"><a href="Math.html">Math</a></div><div class="sidebar-section-children"><a href="Medals.html">Medals</a></div><div class="sidebar-section-children"><a href="Newgrounds.html">Newgrounds</a></div><div class="sidebar-section-children"><a href="Particles.html">Particles</a></div><div class="sidebar-section-children"><a href="PostProcess.html">PostProcess</a></div><div class="sidebar-section-children"><a href="Random.html">Random</a></div><div class="sidebar-section-children"><a href="Settings.html">Settings</a></div><div class="sidebar-section-children"><a href="TileLayers.html">TileLayers</a></div><div class="sidebar-section-children"><a href="UISystem.html">UISystem</a></div><div class="sidebar-section-children"><a href="Utilities.html">Utilities</a></div><div class="sidebar-section-children"><a href="WebGL.html">WebGL</a></div><div class="sidebar-section-children"><a href="ZzFXM.html">ZzFXM</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#light-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>