littlejsengine 1.11.13 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/littlejs.d.ts +1418 -109
  2. package/dist/littlejs.esm.js +3495 -581
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +3258 -399
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +3147 -379
  7. package/examples/box2d/game.js +73 -45
  8. package/examples/box2d/gameObjects.js +96 -92
  9. package/examples/box2d/index.html +2 -7
  10. package/examples/box2d/scenes.js +82 -92
  11. package/examples/breakout/game.js +62 -30
  12. package/examples/breakout/gameObjects.js +45 -47
  13. package/examples/breakout/index.html +1 -5
  14. package/examples/breakoutTutorial/game.js +36 -29
  15. package/examples/breakoutTutorial/index.html +1 -3
  16. package/examples/empty/game.js +5 -2
  17. package/examples/empty/index.html +1 -3
  18. package/examples/htmlMenu/game.js +25 -19
  19. package/examples/htmlMenu/index.html +5 -7
  20. package/examples/index.html +2 -3
  21. package/examples/module/game.js +32 -34
  22. package/examples/module/index.html +1 -1
  23. package/examples/particles/index.html +27 -22
  24. package/examples/platformer/game.js +71 -48
  25. package/examples/platformer/gameCharacter.js +42 -34
  26. package/examples/platformer/gameEffects.js +82 -75
  27. package/examples/platformer/gameLevel.js +67 -38
  28. package/examples/platformer/{gameLevelData.js → gameLevelData.json} +1 -11
  29. package/examples/platformer/gameObjects.js +70 -64
  30. package/examples/platformer/gamePlayer.js +12 -7
  31. package/examples/platformer/index.html +1 -9
  32. package/examples/puzzle/game.js +58 -42
  33. package/examples/puzzle/index.html +1 -3
  34. package/examples/shorts/base.html +2 -2
  35. package/examples/shorts/particles.js +1 -1
  36. package/examples/shorts/platformer.js +1 -1
  37. package/examples/shorts/tileLayer.js +5 -6
  38. package/examples/shorts/tiles.png +0 -0
  39. package/examples/starter/build.js +4 -4
  40. package/examples/starter/game.js +9 -12
  41. package/examples/starter/index.html +13 -13
  42. package/examples/stress/index.html +44 -43
  43. package/examples/typescript/build.js +17 -18
  44. package/examples/typescript/game.js +32 -34
  45. package/examples/typescript/game.ts +32 -34
  46. package/examples/typescript/index.html +1 -1
  47. package/examples/uiSystem/game.js +33 -36
  48. package/examples/uiSystem/index.html +1 -5
  49. package/package.json +3 -3
  50. package/plugins/box2d.js +1556 -640
  51. package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
  52. package/plugins/newgrounds.js +7 -8
  53. package/plugins/pluginExport.js +50 -0
  54. package/plugins/postProcess.js +94 -93
  55. package/plugins/uiSystem.js +145 -161
  56. package/plugins/zzfxm.js +163 -0
  57. package/reference.md +29 -27
  58. package/src/engine.js +15 -20
  59. package/src/engineAudio.js +74 -204
  60. package/src/engineBuild.js +29 -4
  61. package/src/engineDebug.js +105 -9
  62. package/src/engineDraw.js +27 -14
  63. package/src/engineExport.js +12 -8
  64. package/src/engineInput.js +3 -1
  65. package/src/engineObject.js +18 -14
  66. package/src/engineParticles.js +6 -1
  67. package/src/engineRelease.js +6 -1
  68. package/src/engineSettings.js +8 -8
  69. package/src/engineTileLayer.js +179 -96
  70. package/src/engineUtilities.js +5 -11
  71. package/src/engineWebGL.js +19 -7
  72. package/examples/starter/build/index.html +0 -2
  73. package/examples/starter/build/index.js +0 -1
  74. package/examples/starter/build/tiles.png +0 -0
  75. package/examples/starter/game.zip +0 -0
  76. package/examples/typescript/build/dist/littlejs.esm.js +0 -4834
  77. package/examples/typescript/build/examples/typescript/build.js +0 -24
  78. package/examples/typescript/build/examples/typescript/game.js +0 -102
  79. /package/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
package/reference.md CHANGED
@@ -13,7 +13,7 @@ To start LittleJS, you need to create a few functions and pass them to engineIni
13
13
 
14
14
  ```javascript
15
15
  // Start up LittleJS engine with your callback functions
16
- engineInit(init, update, updatePost, render, renderPost, imageSources=['tiles.png']);
16
+ engineInit(init, update, updatePost, render, renderPost, imageSources=[]);
17
17
  ```
18
18
 
19
19
  ## LittleJS Utilities Classes and Functions
@@ -29,7 +29,7 @@ engineInit(init, update, updatePost, render, renderPost, imageSources=['tiles.pn
29
29
  vec2(x=0, y=x) // Create a 2D vector with Vector or floats
30
30
  rgb(r=1, g=1, b=1, a=1) // Create a color object with RGBA values
31
31
  hsl(h=0, s=0, l=1, a=1) // Create a color object with HSLA values
32
- tile(pos=(0,0), size, textureIndex=0) // Create a tile info object
32
+ tile(pos, size=(16,16), texture=0, padding=0) // Create a tile info object
33
33
 
34
34
  // Helper functions
35
35
  abs(value) // Get absolute value
@@ -131,12 +131,12 @@ Timer.valueOf() // Get how long since elapsed, 0 if not set
131
131
 
132
132
  ```javascript
133
133
  // Drawing functions
134
- drawTile(pos, size=(1,1), tileInfo, color, angle=0, mirror, additiveColor)
135
- drawRect(pos, size=(1,1), color=(1,1,1,1), angle=0)
136
- drawLine(posA, posB, thickness=.1, color=(1,1,1,1))
134
+ drawTile(pos, size=(1,1), tileInfo, color=WHITE, angle=0, mirror, additiveColor)
135
+ drawRect(pos, size=(1,1), color=WHITE, angle=0)
136
+ drawLine(posA, posB, thickness=.1, color=WHITE)
137
137
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
138
- drawText(text, pos, size=1, color=(1,1,1,1), lineWidth, lineColor)
139
- drawTextScreen(text, pos, size=1, color=(1,1,1,1), lineWidth, lineColor)
138
+ drawText(text, pos, size=1, color=WHITE, lineWidth, lineColor)
139
+ drawTextScreen(text, pos, size=1, color=WHITE, lineWidth, lineColor)
140
140
  setBlendMode(additive)
141
141
  toggleFullscreen()
142
142
  isFullscreen()
@@ -172,10 +172,15 @@ getCameraSize() // Get the camera's visible area in world space
172
172
  canvasMaxSize = (1920, 1200) // The max size of the canvas
173
173
  canvasFixedSize = (0, 0) // Fixed size of the canvas
174
174
  fontDefault = 'arial' // Default font used for text rendering
175
- canvasPixelated = true // Disable filtering for crisper pixel art?
176
- showSplashScreen = false // Show the LittleJS splash screen on startup?
177
- glEnable = true // Enable fast WebGL rendering?
178
- glOverlay = true // Prevent compositing the WebGL canvas?
175
+ canvasPixelated = true // Use nearest neighbor canvas scaling for more pixelated look
176
+ tilesPixelated = true // Disable filtering for crisper pixel art
177
+ showSplashScreen = false // Show the LittleJS splash screen on startup
178
+ glEnable = true // Enable fast WebGL rendering
179
+ glOverlay = true // Prevent compositing the WebGL canvas
180
+
181
+ // Tile sheet settings
182
+ tileSizeDefault = (16,16) // Default size of tiles in pixels
183
+ tileFixBleedScale = .3 // How much smaller to draw tiles to prevent bleeding
179
184
  ```
180
185
 
181
186
  ## LittleJS Audio System
@@ -224,7 +229,7 @@ soundDefaultTaper = .7 // Default range percent to taper off sound (0-1)
224
229
  keyIsDown(key) // Is key down?
225
230
  keyWasPressed(key) // Was key pressed this frame?
226
231
  keyWasReleased(key) // Was key released this frame?
227
- keyDirection() // Get input vector from arrow keys or wasd
232
+ keyDirection(up, down, left, right) // Get input vector from arrow keys or wasd
228
233
 
229
234
  // Mouse / Touch
230
235
  mousePos // World space mouse position
@@ -276,7 +281,7 @@ touchGamepadAlpha = .3 // Transparency of touch gamepad overlay
276
281
 
277
282
  ```javascript
278
283
  // Engine Object
279
- EngineObject(pos=(0,0), size=(1,1), tileInfo, angle=0, color, renderOrder=0)
284
+ EngineObject(pos, size=(1,1), tileInfo, angle=0, color, renderOrder=0)
280
285
  EngineObject.update() // Update object, called automatically
281
286
  EngineObject.render() // Render object, called automatically
282
287
  EngineObject.destroy() // Destroy this object and children
@@ -317,7 +322,7 @@ objectDefaultAngleDamping = 1 // How much to slow angular velocity each frame (0
317
322
  objectDefaultElasticity = 0 // How much to bounce when a collision occurs (0-1)
318
323
  objectDefaultFriction = .8 // How much to slow when touching (0-1)
319
324
  objectMaxSpeed = 1 // Clamp max speed to avoid fast objects missing collisions
320
- gravity = 0 // How much gravity to apply to objects
325
+ gravity = (0,0) // How much gravity to apply to objects
321
326
 
322
327
  // Engine Object functions
323
328
  engineObjectsCollect(pos, size, objects=engineObjects)k
@@ -336,15 +341,8 @@ engineObjectsDestroy()
336
341
  - Drawn directly to the main canvas without using WebGL
337
342
 
338
343
  ```javascript
339
- // Tile Collision System
340
- tileCollisionSize // Size of the tile collision layer
341
- initTileCollision(size) // Clear and initialize tile collision
342
- setTileCollisionData(pos, data=0) // Set tile collision data at pos
343
- getTileCollisionData(pos) // Get tile collision data at pos
344
- tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
345
- tileCollisionRaycast(posStart, posEnd, object) // Return the center of tile if hit
346
-
347
- // Tile Layer Object
344
+
345
+ // LittleJS Layer System
348
346
  TileLayer(position, size, tileInfo, scale) // Create a tile layer object
349
347
  TileLayer.setData(layerPos, data, redraw) // Set data at position
350
348
  TileLayer.getData(layerPos) // Get data at position
@@ -355,12 +353,16 @@ TileLayer.drawTile(pos, size=(1,1), tileInfo, color, angle, mirror) // Draw tile
355
353
  TileLayer.drawCanvas2D(pos, size, angle, mirror, drawFunction) // Draw to 2D canvas
356
354
 
357
355
  // Tile Layer Data Object
358
- TileLayerData(tile, direction=0, mirror=false, color=(1,1,1,1)) // Create tile data object
356
+ TileLayerData(tile, direction=0, mirror=false, color=WHITE) // Create tile data object
359
357
  TileLayerData.clear() // Clear this tile data
360
358
 
361
- // Tile sheet settings
362
- tileSizeDefault = (16,16) // Default size of tiles in pixels
363
- tileFixBleedScale = .3 // How much smaller to draw tiles to prevent bleeding
359
+ // Tile Collision Layer
360
+ TileCollisionLayer(position, size, tileInfo=tile()) // Create a tile collision layer object
361
+ TileCollisionLayer.size // Size of the tile collision layer
362
+ TileCollisionLayer.setCollisionData(pos, data=1) // Set tile collision data at pos
363
+ getTileCollisionData(pos) // Get tile collision data at pos
364
+ tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
365
+ tileCollisionRaycast(posStart, posEnd, object) // Return the center of tile if hit
364
366
  ```
365
367
 
366
368
  ## LittleJS Particle System
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {string}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.11.13';
33
+ const engineVersion = '1.12.4';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -118,16 +118,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
118
118
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
119
119
 
120
120
  // allow passing in empty functions
121
- if (!gameInit)
122
- gameInit = ()=>{};
123
- if (!gameUpdate)
124
- gameUpdate = ()=>{};
125
- if (!gameUpdatePost)
126
- gameUpdatePost = ()=>{};
127
- if (!gameRender)
128
- gameRender = ()=>{};
129
- if (!gameRenderPost)
130
- gameRenderPost = ()=>{};
121
+ gameInit ||= ()=>{};
122
+ gameUpdate ||= ()=>{};
123
+ gameUpdatePost ||= ()=>{};
124
+ gameRender ||= ()=>{};
125
+ gameRenderPost ||= ()=>{};
131
126
 
132
127
  // Called automatically by engine to setup render system
133
128
  function enginePreRender()
@@ -154,11 +149,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
154
149
  const debugSpeedUp = debug && keyIsDown('Equal'); // +
155
150
  const debugSpeedDown = debug && keyIsDown('Minus'); // -
156
151
  if (debug) // +/- to speed/slow time
157
- frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
152
+ frameTimeDeltaMS *= debugSpeedUp ? 10 : debugSpeedDown ? .1 : 1;
158
153
  timeReal += frameTimeDeltaMS / 1e3;
159
154
  frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
160
155
  if (!debugSpeedUp)
161
- frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp in case of slow framerate
156
+ frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
157
+ if (debug && debugVideoCaptureIsActive())
158
+ frameTimeBufferMS = 0; // disable time smoothing when capturing video
162
159
 
163
160
  updateCanvas();
164
161
 
@@ -237,6 +234,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
237
234
  }
238
235
  }
239
236
 
237
+ debugVideoCaptureUpdate();
240
238
  requestAnimationFrame(engineUpdate);
241
239
  }
242
240
 
@@ -284,11 +282,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
284
282
 
285
283
  // setup html
286
284
  const styleRoot =
287
- 'margin:0;overflow:hidden;' + // fill the window
288
- 'width:100vw;height:100vh;' + // fill the window
289
- 'display:flex;' + // use flexbox
290
- 'align-items:center;' + // horizontal center
291
- 'justify-content:center;' + // vertical center
285
+ 'margin:0;' + // fill the window
292
286
  'background:#000;' + // set background color
293
287
  (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
294
288
  'user-select:none;' + // prevent hold to select
@@ -311,7 +305,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
311
305
  overlayContext = overlayCanvas.getContext('2d');
312
306
 
313
307
  // set canvas style
314
- const styleCanvas = 'position:absolute'; // allow canvases to overlap
308
+ const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
309
+ 'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
315
310
  mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
316
311
  if (glCanvas)
317
312
  glCanvas.style.cssText = styleCanvas;
@@ -322,12 +317,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
322
317
  new Promise(resolve =>
323
318
  {
324
319
  const image = new Image;
325
- image.crossOrigin = 'anonymous';
326
320
  image.onerror = image.onload = ()=>
327
321
  {
328
322
  textureInfos[textureIndex] = new TextureInfo(image);
329
323
  resolve();
330
324
  }
325
+ image.crossOrigin = 'anonymous';
331
326
  image.src = src;
332
327
  })
333
328
  );
@@ -19,16 +19,15 @@ let audioContext = new AudioContext;
19
19
  /** Master gain node for all audio to pass through
20
20
  * @type {GainNode}
21
21
  * @memberof Audio */
22
- let audioGainNode;
22
+ let audioMasterGain;
23
23
 
24
24
  function audioInit()
25
25
  {
26
26
  if (!soundEnable || headlessMode) return;
27
27
 
28
- // (createGain is more widely supported then GainNode constructor)
29
- audioGainNode = audioContext.createGain();
30
- audioGainNode.connect(audioContext.destination);
31
- audioGainNode.gain.value = soundVolume; // set starting value
28
+ audioMasterGain = audioContext.createGain();
29
+ audioMasterGain.connect(audioContext.destination);
30
+ audioMasterGain.gain.value = soundVolume; // set starting value
32
31
  }
33
32
 
34
33
  ///////////////////////////////////////////////////////////////////////////////
@@ -157,6 +156,8 @@ class Sound
157
156
  isLoading() { return !this.sampleChannels; }
158
157
  }
159
158
 
159
+ ///////////////////////////////////////////////////////////////////////////////
160
+
160
161
  /**
161
162
  * Sound Wave Object - Stores a wave sound for later use and can be played positionally
162
163
  * - this can be used to play wave, mp3, and ogg files
@@ -208,59 +209,7 @@ function playAudioFile(filename, volume=1, loop=false)
208
209
  return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
209
210
  }
210
211
 
211
- /**
212
- * Music Object - Stores a zzfx music track for later use
213
- *
214
- * <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
215
- * @example
216
- * // create some music
217
- * const music_example = new Music(
218
- * [
219
- * [ // instruments
220
- * [,0,400] // simple note
221
- * ],
222
- * [ // patterns
223
- * [ // pattern 1
224
- * [ // channel 0
225
- * 0, -1, // instrument 0, left speaker
226
- * 1, 0, 9, 1 // channel notes
227
- * ],
228
- * [ // channel 1
229
- * 0, 1, // instrument 0, right speaker
230
- * 0, 12, 17, -1 // channel notes
231
- * ]
232
- * ],
233
- * ],
234
- * [0, 0, 0, 0], // sequence, play pattern 0 four times
235
- * 90 // BPM
236
- * ]);
237
- *
238
- * // play the music
239
- * music_example.play();
240
- */
241
- class Music extends Sound
242
- {
243
- /** Create a music object and cache the zzfx music samples for later use
244
- * @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
245
- */
246
- constructor(zzfxMusic)
247
- {
248
- super(undefined);
249
-
250
- if (!soundEnable || headlessMode) return;
251
- this.randomness = 0;
252
- this.sampleChannels = zzfxM(...zzfxMusic);
253
- this.sampleRate = zzfxR;
254
- }
255
-
256
- /** Play the music
257
- * @param {number} [volume=1] - How much to scale volume by
258
- * @param {boolean} [loop] - True if the music should loop
259
- * @return {AudioBufferSourceNode} - The audio source node
260
- */
261
- playMusic(volume, loop=false)
262
- { return super.play(undefined, volume, 1, 1, loop); }
263
- }
212
+ ///////////////////////////////////////////////////////////////////////////////
264
213
 
265
214
  /** Speak text with passed in settings
266
215
  * @param {string} text - The text to speak
@@ -332,7 +281,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
332
281
  // create and connect gain node
333
282
  gainNode = gainNode || audioContext.createGain();
334
283
  gainNode.gain.value = volume;
335
- gainNode.connect(audioGainNode);
284
+ gainNode.connect(audioMasterGain);
336
285
 
337
286
  // connect source to stereo panner and gain
338
287
  const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
@@ -352,7 +301,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
352
301
  }
353
302
 
354
303
  ///////////////////////////////////////////////////////////////////////////////
355
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.1 by Frank Force
304
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
356
305
 
357
306
  /** Generate and play a ZzFX sound
358
307
  *
@@ -394,21 +343,45 @@ const zzfxR = 44100;
394
343
  */
395
344
  function zzfxG
396
345
  (
397
- // parameters
398
- volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
399
- release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
400
- pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
401
- bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
346
+ volume = 1,
347
+ randomness = .05,
348
+ frequency = 220,
349
+ attack = 0,
350
+ sustain = 0,
351
+ release = .1,
352
+ shape = 0,
353
+ shapeCurve = 1,
354
+ slide = 0,
355
+ deltaSlide = 0,
356
+ pitchJump = 0,
357
+ pitchJumpTime = 0,
358
+ repeatTime = 0,
359
+ noise = 0,
360
+ modulation = 0,
361
+ bitCrush = 0,
362
+ delay = 0,
363
+ sustainVolume = 1,
364
+ decay = 0,
365
+ tremolo = 0,
366
+ filter = 0
402
367
  )
403
368
  {
404
- // LJS Note: ZZFX modded so randomness is handled by Sound class
405
-
406
369
  // init parameters
407
- let PI2 = PI*2, sampleRate = zzfxR,
370
+ let sampleRate = zzfxR,
371
+ PI2 = PI*2,
408
372
  startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
409
373
  startFrequency = frequency *=
410
- rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
411
- b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
374
+ (1 + rand(randomness,-randomness)) * PI2 / sampleRate,
375
+ modOffset = 0, // modulation offset
376
+ repeat = 0, // repeat offset
377
+ crush = 0, // bit crush offset
378
+ jump = 1, // pitch jump timer
379
+ length, // sample length
380
+ b = [], // sample buffer
381
+ t = 0, // sample time
382
+ i = 0, // sample index
383
+ s = 0, // sample value
384
+ f, // wave frequency
412
385
 
413
386
  // biquad LP/HP filter
414
387
  quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
@@ -418,35 +391,37 @@ function zzfxG
418
391
  b1 = -(sign(filter) + cos) / a0, b2 = b0,
419
392
  x2 = 0, x1 = 0, y2 = 0, y1 = 0;
420
393
 
421
- // scale by sample rate
422
- attack = attack * sampleRate + 9; // minimum attack to prevent pop
423
- decay *= sampleRate;
424
- sustain *= sampleRate;
425
- release *= sampleRate;
426
- delay *= sampleRate;
427
- deltaSlide *= 500 * PI2 / sampleRate**3;
428
- modulation *= PI2 / sampleRate;
429
- pitchJump *= PI2 / sampleRate;
430
- pitchJumpTime *= sampleRate;
431
- repeatTime = repeatTime * sampleRate | 0;
394
+ // scale by sample rate
395
+ const minAttack = 9; // prevent pop if attack is 0
396
+ attack = attack * sampleRate || minAttack;
397
+ decay *= sampleRate;
398
+ sustain *= sampleRate;
399
+ release *= sampleRate;
400
+ delay *= sampleRate;
401
+ deltaSlide *= 500 * PI2 / sampleRate**3;
402
+ modulation *= PI2 / sampleRate;
403
+ pitchJump *= PI2 / sampleRate;
404
+ pitchJumpTime *= sampleRate;
405
+ repeatTime = repeatTime * sampleRate | 0;
432
406
 
433
407
  // generate waveform
434
408
  for(length = attack + decay + sustain + release + delay | 0;
435
- i < length; b[i++] = s * volume) // sample
409
+ i < length; b[i++] = s * volume) // sample
436
410
  {
437
- if (!(++c%(bitCrush*100|0))) // bit crush
411
+ if (!(++crush%(bitCrush*100|0))) // bit crush
438
412
  {
439
- s = shape? shape>1? shape>2? shape>3? // wave shape
440
- Math.sin(t**3) : // 4 noise
441
- clamp(Math.tan(t),1,-1): // 3 tan
442
- 1-(2*t/PI2%2+2)%2: // 2 saw
443
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
444
- Math.sin(t); // 0 sin
413
+ s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
414
+ (t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
415
+ Math.sin(t**3) : // 4 noise
416
+ Math.max(Math.min(Math.tan(t),1),-1): // 3 tan
417
+ 1-(2*t/PI2%2+2)%2: // 2 saw
418
+ 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
419
+ Math.sin(t); // 0 sin
445
420
 
446
421
  s = (repeatTime ?
447
422
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
448
423
  : 1) *
449
- sign(s)*(abs(s)**shapeCurve) * // curve
424
+ (shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
450
425
  (i < attack ? i/attack : // attack
451
426
  i < attack + decay ? // decay
452
427
  1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
@@ -461,133 +436,28 @@ function zzfxG
461
436
  (i<length-delay? 1 : (length-i)/delay) * // release delay
462
437
  b[i-delay|0]/2/volume) : s; // sample delay
463
438
 
464
- if (filter) // apply filter
439
+ if (filter) // apply filter
465
440
  s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
466
441
  }
467
442
 
468
443
  f = (frequency += slide += deltaSlide) *// frequency
469
- Math.cos(modulation*tm++); // modulation
444
+ Math.cos(modulation*modOffset++); // modulation
470
445
  t += f + f*noise*Math.sin(i**5); // noise
471
446
 
472
- if (j && ++j > pitchJumpTime) // pitch jump
447
+ if (jump && ++jump > pitchJumpTime) // pitch jump
473
448
  {
474
449
  frequency += pitchJump; // apply pitch jump
475
450
  startFrequency += pitchJump; // also apply to start
476
- j = 0; // stop pitch jump time
451
+ jump = 0; // stop pitch jump time
477
452
  }
478
453
 
479
- if (repeatTime && !(++r % repeatTime)) // repeat
454
+ if (repeatTime && !(++repeat % repeatTime)) // repeat
480
455
  {
481
- frequency = startFrequency; // reset frequency
482
- slide = startSlide; // reset slide
483
- j = j || 1; // reset pitch jump time
456
+ frequency = startFrequency; // reset frequency
457
+ slide = startSlide; // reset slide
458
+ jump ||= 1; // reset pitch jump time
484
459
  }
485
460
  }
486
461
 
487
- return b;
462
+ return b; // return sample buffer
488
463
  }
489
-
490
- ///////////////////////////////////////////////////////////////////////////////
491
- // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
492
-
493
- /** Generate samples for a ZzFM song with given parameters
494
- * @param {Array} instruments - Array of ZzFX sound parameters
495
- * @param {Array} patterns - Array of pattern data
496
- * @param {Array} sequence - Array of pattern indexes
497
- * @param {number} [BPM] - Playback speed of the song in BPM
498
- * @return {Array} - Left and right channel sample data
499
- * @memberof Audio */
500
- function zzfxM(instruments, patterns, sequence, BPM = 125)
501
- {
502
- let i, j, k;
503
- let instrumentParameters;
504
- let note;
505
- let sample;
506
- let patternChannel;
507
- let notFirstBeat;
508
- let stop;
509
- let instrument;
510
- let attenuation;
511
- let outSampleOffset;
512
- let isSequenceEnd;
513
- let sampleOffset = 0;
514
- let nextSampleOffset;
515
- let sampleBuffer = [];
516
- let leftChannelBuffer = [];
517
- let rightChannelBuffer = [];
518
- let channelIndex = 0;
519
- let panning = 0;
520
- let hasMore = 1;
521
- let sampleCache = {};
522
- let beatLength = zzfxR / BPM * 60 >> 2;
523
-
524
- // for each channel in order until there are no more
525
- for (; hasMore; channelIndex++) {
526
-
527
- // reset current values
528
- sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
529
-
530
- // for each pattern in sequence
531
- sequence.forEach((patternIndex, sequenceIndex) => {
532
- // get pattern for current channel, use empty 1 note pattern if none found
533
- patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
534
-
535
- // check if there are more channels
536
- hasMore |= patterns[patternIndex][channelIndex]&&1;
537
-
538
- // get next offset, use the length of first channel
539
- nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
540
- // for each beat in pattern, plus one extra if end of sequence
541
- isSequenceEnd = sequenceIndex == sequence.length - 1;
542
- for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
543
-
544
- // <channel-note>
545
- note = patternChannel[i];
546
-
547
- // stop if end, different instrument or new note
548
- stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
549
- instrument != (patternChannel[0] || 0) || note | 0;
550
-
551
- // fill buffer with samples for previous beat, most cpu intensive part
552
- for (j = 0; j < beatLength && notFirstBeat;
553
-
554
- // fade off attenuation at end of beat if stopping note, prevents clicking
555
- j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
556
- ) {
557
- // copy sample to stereo buffers with panning
558
- sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
559
- leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
560
- rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
561
- }
562
-
563
- // set up for next note
564
- if (note) {
565
- // set attenuation
566
- attenuation = note % 1;
567
- panning = patternChannel[1] || 0;
568
- if (note |= 0) {
569
- // get cached sample
570
- sampleBuffer = sampleCache[
571
- [
572
- instrument = patternChannel[sampleOffset = 0] || 0,
573
- note
574
- ]
575
- ] = sampleCache[[instrument, note]] || (
576
- // add sample to cache
577
- instrumentParameters = [...instruments[instrument]],
578
- instrumentParameters[2] *= 2 ** ((note - 12) / 12),
579
-
580
- // allow negative values to stop notes
581
- note > 0 ? zzfxG(...instrumentParameters) : []
582
- );
583
- }
584
- }
585
- }
586
-
587
- // update the sample offset
588
- outSampleOffset = nextSampleOffset;
589
- });
590
- }
591
-
592
- return [leftChannelBuffer, rightChannelBuffer];
593
- }
@@ -13,6 +13,7 @@
13
13
  const ENGINE_NAME = 'littlejs';
14
14
  const BUILD_FOLDER = 'dist';
15
15
  const SOURCE_FOLDER = 'src';
16
+ const PLUGIN_FOLDER = 'plugins';
16
17
  const engineSourceFiles =
17
18
  [
18
19
  `${SOURCE_FOLDER}/engineUtilities.js`,
@@ -27,6 +28,14 @@ const engineSourceFiles =
27
28
  `${SOURCE_FOLDER}/engineWebGL.js`,
28
29
  `${SOURCE_FOLDER}/engine.js`,
29
30
  ];
31
+ const enginePluginFiles =
32
+ [
33
+ `${PLUGIN_FOLDER}/newgrounds.js`,
34
+ `${PLUGIN_FOLDER}/postProcess.js`,
35
+ `${PLUGIN_FOLDER}/zzfxm.js`,
36
+ `${PLUGIN_FOLDER}/uiSystem.js`,
37
+ `${PLUGIN_FOLDER}/box2d.js`,
38
+ ];
30
39
  const asciiArt =`
31
40
  ~~~~°°°°ooo°oOo°ooOooOooOo.
32
41
  __________ ________ ____'°oO.
@@ -55,7 +64,11 @@ Build
55
64
  (
56
65
  'Build Engine -- all',
57
66
  `${BUILD_FOLDER}/${ENGINE_NAME}.js`,
58
- [`${SOURCE_FOLDER}/engineDebug.js`, ...engineSourceFiles],
67
+ [
68
+ `${SOURCE_FOLDER}/engineDebug.js`,
69
+ ...engineSourceFiles,
70
+ ...enginePluginFiles
71
+ ],
59
72
  [], true
60
73
  );
61
74
 
@@ -63,7 +76,11 @@ Build
63
76
  (
64
77
  'Build Engine -- release',
65
78
  `${BUILD_FOLDER}/${ENGINE_NAME}.release.js`,
66
- [`${SOURCE_FOLDER}/engineRelease.js`, ...engineSourceFiles],
79
+ [
80
+ `${SOURCE_FOLDER}/engineRelease.js`,
81
+ ...engineSourceFiles,
82
+ ...enginePluginFiles
83
+ ],
67
84
  [], true
68
85
  );
69
86
 
@@ -79,7 +96,11 @@ Build
79
96
  (
80
97
  'Build Engine -- ESM',
81
98
  `${BUILD_FOLDER}/${ENGINE_NAME}.esm.js`,
82
- [`${BUILD_FOLDER}/${ENGINE_NAME}.js`, `${SOURCE_FOLDER}/engineExport.js`],
99
+ [
100
+ `${BUILD_FOLDER}/${ENGINE_NAME}.js`,
101
+ `${SOURCE_FOLDER}/engineExport.js`,
102
+ `${PLUGIN_FOLDER}/pluginExport.js`
103
+ ],
83
104
  [typeScriptBuildStep]
84
105
  );
85
106
 
@@ -87,7 +108,11 @@ Build
87
108
  (
88
109
  'Build Engine -- ESM minified release',
89
110
  `${BUILD_FOLDER}/${ENGINE_NAME}.esm.min.js`,
90
- [`${BUILD_FOLDER}/${ENGINE_NAME}.min.js`, `${SOURCE_FOLDER}/engineExport.js`],
111
+ [
112
+ `${BUILD_FOLDER}/${ENGINE_NAME}.release.js`,
113
+ `${SOURCE_FOLDER}/engineExport.js`,
114
+ `${PLUGIN_FOLDER}/pluginExport.js`
115
+ ],
91
116
  [uglifyBuildStep]
92
117
  );
93
118