littlejsengine 1.8.8 → 1.8.9

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.
@@ -2819,7 +2819,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate
2819
2819
  }
2820
2820
 
2821
2821
  ///////////////////////////////////////////////////////////////////////////////
2822
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
2822
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.0 by Frank Force
2823
2823
 
2824
2824
  /** Generate and play a ZzFX sound
2825
2825
  *
@@ -2855,6 +2855,7 @@ const zzfxR = 44100;
2855
2855
  * @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
2856
2856
  * @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
2857
2857
  * @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
2858
+ * @param {Number} [filter=0] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
2858
2859
  * @return {Array} - Array of audio samples
2859
2860
  * @memberof Audio
2860
2861
  */
@@ -2864,78 +2865,91 @@ function zzfxG
2864
2865
  volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
2865
2866
  release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
2866
2867
  pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
2867
- bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
2868
+ bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
2868
2869
  )
2869
2870
  {
2870
- // locals
2871
- let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2872
- startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
2873
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
2871
+ // init parameters
2872
+ let PI2 = PI*2, sampleRate = zzfxR,
2873
+ startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
2874
+ startFrequency = frequency *=
2875
+ rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
2876
+ b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
2877
+
2878
+ // biquad LP/HP filter
2879
+ quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
2880
+ cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
2881
+ a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
2882
+ b0 = (1 + sign(filter) * cos) / 2 / a0,
2883
+ b1 = -(sign(filter) + cos) / a0, b2 = b0,
2884
+ x2 = 0, x1 = 0, y2 = 0, y1 = 0;
2874
2885
 
2875
2886
  // scale by sample rate
2876
- attack = attack * zzfxR + 9; // minimum attack to prevent pop
2877
- decay *= zzfxR;
2878
- sustain *= zzfxR;
2879
- release *= zzfxR;
2880
- delay *= zzfxR;
2881
- deltaSlide *= 500 * PI2 / zzfxR**3;
2882
- modulation *= PI2 / zzfxR;
2883
- pitchJump *= PI2 / zzfxR;
2884
- pitchJumpTime *= zzfxR;
2885
- repeatTime = repeatTime * zzfxR | 0;
2887
+ attack = attack * sampleRate + 9; // minimum attack to prevent pop
2888
+ decay *= sampleRate;
2889
+ sustain *= sampleRate;
2890
+ release *= sampleRate;
2891
+ delay *= sampleRate;
2892
+ deltaSlide *= 500 * PI2 / sampleRate**3;
2893
+ modulation *= PI2 / sampleRate;
2894
+ pitchJump *= PI2 / sampleRate;
2895
+ pitchJumpTime *= sampleRate;
2896
+ repeatTime = repeatTime * sampleRate | 0;
2897
+ volume *= soundVolume;
2886
2898
 
2887
2899
  // generate waveform
2888
- for (length = attack + decay + sustain + release + delay | 0;
2889
- i < length; b[i++] = s)
2900
+ for(length = attack + decay + sustain + release + delay | 0;
2901
+ i < length; b[i++] = s * volume) // sample
2890
2902
  {
2891
- if (!(++c%(bitCrush*100|0))) // bit crush
2903
+ if (!(++c%(bitCrush*100|0))) // bit crush
2892
2904
  {
2893
- s = shape? shape>1? shape>2? shape>3? // wave shape
2894
- Math.sin((t%PI2)**3) : // 4 noise
2895
- max(min(Math.tan(t),1),-1): // 3 tan
2896
- 1-(2*t/PI2%2+2)%2: // 2 saw
2897
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2898
- Math.sin(t); // 0 sin
2899
-
2905
+ s = shape? shape>1? shape>2? shape>3? // wave shape
2906
+ Math.sin(t*t) : // 4 noise
2907
+ clamp(Math.tan(t),1,-1): // 3 tan
2908
+ 1-(2*t/PI2%2+2)%2: // 2 saw
2909
+ 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2910
+ Math.sin(t); // 0 sin
2911
+
2900
2912
  s = (repeatTime ?
2901
2913
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
2902
2914
  : 1) *
2903
- sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
2904
- volume * soundVolume * ( // envelope
2905
- i < attack ? i/attack : // attack
2906
- i < attack + decay ? // decay
2907
- 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
2908
- i < attack + decay + sustain ? // sustain
2909
- sustainVolume : // sustain volume
2910
- i < length - delay ? // release
2911
- (length - i - delay)/release * // release falloff
2912
- sustainVolume : // release volume
2913
- 0); // post release
2914
-
2915
- s = delay ? s/2 + (delay > i ? 0 : // delay
2916
- (i<length-delay? 1 : (length-i)/delay) * // release delay
2917
- b[i-delay|0]/2) : s; // sample delay
2918
- }
2919
-
2920
- f = (frequency += slide += deltaSlide) * // frequency
2921
- Math.cos(modulation*tm++); // modulation
2922
- t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
2923
-
2924
- if (j && ++j > pitchJumpTime) // pitch jump
2925
- {
2926
- frequency += pitchJump; // apply pitch jump
2927
- startFrequency += pitchJump; // also apply to start
2928
- j = 0; // reset pitch jump time
2915
+ sign(s)*(abs(s)**shapeCurve) * // curve
2916
+ (i < attack ? i/attack : // attack
2917
+ i < attack + decay ? // decay
2918
+ 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
2919
+ i < attack + decay + sustain ? // sustain
2920
+ sustainVolume : // sustain volume
2921
+ i < length - delay ? // release
2922
+ (length - i - delay)/release * // release falloff
2923
+ sustainVolume : // release volume
2924
+ 0); // post release
2925
+
2926
+ s = delay ? s/2 + (delay > i ? 0 : // delay
2927
+ (i<length-delay? 1 : (length-i)/delay) * // release delay
2928
+ b[i-delay|0]/2/volume) : s; // sample delay
2929
+
2930
+ if (filter) // apply filter
2931
+ s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
2929
2932
  }
2930
2933
 
2931
- if (repeatTime && !(++r % repeatTime)) // repeat
2932
- {
2933
- frequency = startFrequency; // reset frequency
2934
- slide = startSlide; // reset slide
2935
- j ||= 1; // reset pitch jump time
2934
+ f = (frequency += slide += deltaSlide) *// frequency
2935
+ Math.cos(modulation*tm++); // modulation
2936
+ t += f + f*noise*Math.sin(i**5); // noise
2937
+
2938
+ if (j && ++j > pitchJumpTime) // pitch jump
2939
+ {
2940
+ frequency += pitchJump; // apply pitch jump
2941
+ startFrequency += pitchJump; // also apply to start
2942
+ j = 0; // stop pitch jump time
2943
+ }
2944
+
2945
+ if (repeatTime && !(++r % repeatTime)) // repeat
2946
+ {
2947
+ frequency = startFrequency; // reset frequency
2948
+ slide = startSlide; // reset slide
2949
+ j = j || 1; // reset pitch jump time
2936
2950
  }
2937
2951
  }
2938
-
2952
+
2939
2953
  return b;
2940
2954
  }
2941
2955
 
@@ -3431,13 +3445,13 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3431
3445
  class ParticleEmitter extends EngineObject
3432
3446
  {
3433
3447
  /** Create a particle system with the given settings
3434
- * @param {Vector2} position - World space position of the emitter
3435
- * @param {Number} [angle=0] - Angle to emit the particles
3436
- * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
3437
- * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3438
- * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3448
+ * @param {Vector2} position - World space position of the emitter
3449
+ * @param {Number} [angle=0] - Angle to emit the particles
3450
+ * @param {Number|Vector2} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
3451
+ * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3452
+ * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3439
3453
  * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3440
- * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3454
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3441
3455
  * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3442
3456
  * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3443
3457
  * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
@@ -3493,7 +3507,7 @@ class ParticleEmitter extends EngineObject
3493
3507
  super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3494
3508
 
3495
3509
  // emitter settings
3496
- /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3510
+ /** @property {Number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3497
3511
  this.emitSize = emitSize
3498
3512
  /** @property {Number} - How long to stay alive (0 is forever) */
3499
3513
  this.emitTime = emitTime;
@@ -4456,7 +4470,7 @@ const engineName = 'LittleJS';
4456
4470
  * @type {String}
4457
4471
  * @default
4458
4472
  * @memberof Engine */
4459
- const engineVersion = '1.8.8';
4473
+ const engineVersion = '1.8.9';
4460
4474
 
4461
4475
  /** Frames per second to update objects
4462
4476
  * @type {Number}
@@ -4778,21 +4792,23 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4778
4792
 
4779
4793
  function drawEngineSplashScreen(t)
4780
4794
  {
4781
- // background
4782
- const grayscale = 0;
4783
4795
  const x = mainContext;
4784
4796
  const w = mainCanvas.width = innerWidth;
4785
4797
  const h = mainCanvas.height = innerHeight;
4786
- const p3 = percent(t, 1, .8);
4787
- const p4 = percent(t, 0, .5);
4788
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
4789
- g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
4790
- g.addColorStop(1,hsl(0,0,0,p3));
4791
- x.save();
4792
- x.fillStyle = g;
4793
- x.fillRect(0,0,w,h);
4794
-
4795
- // logo - fade in and out
4798
+ {
4799
+ // background
4800
+ const p3 = percent(t, 1, .8);
4801
+ const p4 = percent(t, 0, .5);
4802
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
4803
+ g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
4804
+ g.addColorStop(1,hsl(0,0,0,p3));
4805
+ x.save();
4806
+ x.fillStyle = g;
4807
+ x.fillRect(0,0,w,h);
4808
+ }
4809
+
4810
+ // draw LittleJS logo...
4811
+
4796
4812
  const rect = (X, Y, W, H, C)=>
4797
4813
  {
4798
4814
  x.beginPath();
@@ -4817,7 +4833,7 @@ function drawEngineSplashScreen(t)
4817
4833
  C ? x.fill() : x.stroke();
4818
4834
  };
4819
4835
  const color = (c=0, l=0) =>
4820
- hsl([.98,.3,.57,.14][c%4]-10,grayscale?0:.8,[0,.3,.5,.8,.9][l]);
4836
+ hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]);
4821
4837
  const alpha = wave(1,1,t);
4822
4838
  const p = percent(alpha, .1, .5);
4823
4839
 
@@ -4860,9 +4876,9 @@ function drawEngineSplashScreen(t)
4860
4876
  rect(50,20,6,-10,color(0,2));
4861
4877
  rect(50,20,3,-10,color(0,3));
4862
4878
  rect(50,10,10,10);
4863
- circle(55,2,11,.5,PI-.5,color(3,3));
4864
- circle(55,2,11,.5,PI/2,color(3,2),1);
4865
- circle(55,2,11,.5,PI-.5);
4879
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
4880
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
4881
+ circle(55,2,11.4,.5,PI-.5);
4866
4882
  rect(45,7,20,-7,color(0,2));
4867
4883
  rect(45,0,20,3,color(0,3));
4868
4884
  rect(45,0,20,7);
@@ -4900,8 +4916,9 @@ function drawEngineSplashScreen(t)
4900
4916
  }
4901
4917
 
4902
4918
  // wheels
4903
- rect(5,40,9,6,color());
4904
- rect(15,54,38,-14,color())
4919
+ rect(6,40,5,5);
4920
+ rect(6,40,5,5,color());
4921
+ rect(15,54,38,-14,color());
4905
4922
  for (let i=3; i--;)
4906
4923
  for (let j=2; j--;)
4907
4924
  {
@@ -4910,26 +4927,26 @@ function drawEngineSplashScreen(t)
4910
4927
  circle(15*i+15,47,j?7:1,0,PI,color(i,2));
4911
4928
  x.stroke();
4912
4929
  }
4913
- line(6,40,68,40) // center
4914
- line(77,54,4,54) // bottom
4930
+ line(6,40,68,40); // center
4931
+ line(77,54,4,54); // bottom
4915
4932
 
4916
- // text
4933
+ // draw engine name
4917
4934
  const s = engineName;
4918
4935
  x.font = '900 16px arial';
4919
4936
  x.textAlign = 'center';
4920
4937
  x.textBaseline = 'top';
4921
- x.lineWidth = 1+p*3
4938
+ x.lineWidth = 1+p*3;
4922
4939
  let w2 = 0;
4923
4940
  for (let i=0; i<s.length; ++i)
4924
4941
  w2 += x.measureText(s[i]).width;
4925
4942
  for (let j=2; j--;)
4926
4943
  for (let i=0, X=41-w2/2; i<s.length; ++i)
4927
4944
  {
4928
- x.fillStyle = color(i,grayscale?3:2);
4945
+ x.fillStyle = color(i,2);
4929
4946
  const w = x.measureText(s[i]).width;
4930
4947
  x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
4931
4948
  X += w;
4932
4949
  }
4933
-
4950
+
4934
4951
  x.restore();
4935
4952
  }
package/examples/logo.png CHANGED
Binary file
@@ -18,7 +18,7 @@ const sound_destroyObject =new Sound([.5,,1e3,.02,,.2,1,3,.1,,,,,1,-30,.5,,.5]);
18
18
  const sound_die = new Sound([.5,.4,126,.05,,.2,1,2.09,,-4,,,1,1,1,.4,.03]);
19
19
  const sound_jump = new Sound([.4,.2,250,.04,,.04,,,1,,,,,3]);
20
20
  const sound_dodge = new Sound([.4,.2,150,.05,,.05,,,-1,,,,,4,,,,,.02]);
21
- const sound_walk = new Sound([.3,.1,70,,,.01,4,,,,-9,.1,,,,,,.5]);
21
+ const sound_walk = new Sound([.3,.1,200,,,.01,4,,,,-9,.1,,,,,,.5]);
22
22
  const sound_explosion = new Sound([2,.2,72,.01,.01,.2,4,,,,,,,1,,.5,.1,.5,.02]);
23
23
  const sound_grenade = new Sound([.5,.01,300,,,.02,3,.22,,,-9,.2,,,,,,.5]);
24
24
  const sound_killEnemy = new Sound([,,783,,.03,.02,1,2,,,940,.03,,,,,.2,.6,,.06]);
@@ -111,7 +111,7 @@ function gameRender()
111
111
  drawRect(vec2(16,8), vec2(20,14), hsl(0,0,.6), 0, 0);
112
112
 
113
113
  // draw the logo as a tile
114
- drawTile(vec2(21,5), vec2(4.5), tile(2));
114
+ drawTile(vec2(21,5), vec2(4.5), tile(3,128));
115
115
  }
116
116
 
117
117
  ///////////////////////////////////////////////////////////////////////////////
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.8.8",
3
+ "version": "1.8.9",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "build/littlejs.esm.js",
6
6
  "types": "build/littlejs.esm.js",
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.8.8';
33
+ const engineVersion = '1.8.9';
34
34
 
35
35
  /** Frames per second to update objects
36
36
  * @type {Number}
@@ -352,21 +352,23 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
352
352
 
353
353
  function drawEngineSplashScreen(t)
354
354
  {
355
- // background
356
- const grayscale = 0;
357
355
  const x = mainContext;
358
356
  const w = mainCanvas.width = innerWidth;
359
357
  const h = mainCanvas.height = innerHeight;
360
- const p3 = percent(t, 1, .8);
361
- const p4 = percent(t, 0, .5);
362
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
363
- g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
364
- g.addColorStop(1,hsl(0,0,0,p3));
365
- x.save();
366
- x.fillStyle = g;
367
- x.fillRect(0,0,w,h);
368
-
369
- // logo - fade in and out
358
+ {
359
+ // background
360
+ const p3 = percent(t, 1, .8);
361
+ const p4 = percent(t, 0, .5);
362
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
363
+ g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
364
+ g.addColorStop(1,hsl(0,0,0,p3));
365
+ x.save();
366
+ x.fillStyle = g;
367
+ x.fillRect(0,0,w,h);
368
+ }
369
+
370
+ // draw LittleJS logo...
371
+
370
372
  const rect = (X, Y, W, H, C)=>
371
373
  {
372
374
  x.beginPath();
@@ -391,7 +393,7 @@ function drawEngineSplashScreen(t)
391
393
  C ? x.fill() : x.stroke();
392
394
  };
393
395
  const color = (c=0, l=0) =>
394
- hsl([.98,.3,.57,.14][c%4]-10,grayscale?0:.8,[0,.3,.5,.8,.9][l]);
396
+ hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]);
395
397
  const alpha = wave(1,1,t);
396
398
  const p = percent(alpha, .1, .5);
397
399
 
@@ -434,9 +436,9 @@ function drawEngineSplashScreen(t)
434
436
  rect(50,20,6,-10,color(0,2));
435
437
  rect(50,20,3,-10,color(0,3));
436
438
  rect(50,10,10,10);
437
- circle(55,2,11,.5,PI-.5,color(3,3));
438
- circle(55,2,11,.5,PI/2,color(3,2),1);
439
- circle(55,2,11,.5,PI-.5);
439
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
440
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
441
+ circle(55,2,11.4,.5,PI-.5);
440
442
  rect(45,7,20,-7,color(0,2));
441
443
  rect(45,0,20,3,color(0,3));
442
444
  rect(45,0,20,7);
@@ -474,8 +476,9 @@ function drawEngineSplashScreen(t)
474
476
  }
475
477
 
476
478
  // wheels
477
- rect(5,40,9,6,color());
478
- rect(15,54,38,-14,color())
479
+ rect(6,40,5,5);
480
+ rect(6,40,5,5,color());
481
+ rect(15,54,38,-14,color());
479
482
  for (let i=3; i--;)
480
483
  for (let j=2; j--;)
481
484
  {
@@ -484,26 +487,26 @@ function drawEngineSplashScreen(t)
484
487
  circle(15*i+15,47,j?7:1,0,PI,color(i,2));
485
488
  x.stroke();
486
489
  }
487
- line(6,40,68,40) // center
488
- line(77,54,4,54) // bottom
490
+ line(6,40,68,40); // center
491
+ line(77,54,4,54); // bottom
489
492
 
490
- // text
493
+ // draw engine name
491
494
  const s = engineName;
492
495
  x.font = '900 16px arial';
493
496
  x.textAlign = 'center';
494
497
  x.textBaseline = 'top';
495
- x.lineWidth = 1+p*3
498
+ x.lineWidth = 1+p*3;
496
499
  let w2 = 0;
497
500
  for (let i=0; i<s.length; ++i)
498
501
  w2 += x.measureText(s[i]).width;
499
502
  for (let j=2; j--;)
500
503
  for (let i=0, X=41-w2/2; i<s.length; ++i)
501
504
  {
502
- x.fillStyle = color(i,grayscale?3:2);
505
+ x.fillStyle = color(i,2);
503
506
  const w = x.measureText(s[i]).width;
504
507
  x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
505
508
  X += w;
506
509
  }
507
-
510
+
508
511
  x.restore();
509
512
  }
@@ -327,7 +327,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate
327
327
  }
328
328
 
329
329
  ///////////////////////////////////////////////////////////////////////////////
330
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
330
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.0 by Frank Force
331
331
 
332
332
  /** Generate and play a ZzFX sound
333
333
  *
@@ -363,6 +363,7 @@ const zzfxR = 44100;
363
363
  * @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
364
364
  * @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
365
365
  * @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
366
+ * @param {Number} [filter=0] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
366
367
  * @return {Array} - Array of audio samples
367
368
  * @memberof Audio
368
369
  */
@@ -372,78 +373,91 @@ function zzfxG
372
373
  volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
373
374
  release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
374
375
  pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
375
- bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
376
+ bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
376
377
  )
377
378
  {
378
- // locals
379
- let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
380
- startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
381
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
379
+ // init parameters
380
+ let PI2 = PI*2, sampleRate = zzfxR,
381
+ startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
382
+ startFrequency = frequency *=
383
+ rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
384
+ b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
385
+
386
+ // biquad LP/HP filter
387
+ quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
388
+ cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
389
+ a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
390
+ b0 = (1 + sign(filter) * cos) / 2 / a0,
391
+ b1 = -(sign(filter) + cos) / a0, b2 = b0,
392
+ x2 = 0, x1 = 0, y2 = 0, y1 = 0;
382
393
 
383
394
  // scale by sample rate
384
- attack = attack * zzfxR + 9; // minimum attack to prevent pop
385
- decay *= zzfxR;
386
- sustain *= zzfxR;
387
- release *= zzfxR;
388
- delay *= zzfxR;
389
- deltaSlide *= 500 * PI2 / zzfxR**3;
390
- modulation *= PI2 / zzfxR;
391
- pitchJump *= PI2 / zzfxR;
392
- pitchJumpTime *= zzfxR;
393
- repeatTime = repeatTime * zzfxR | 0;
395
+ attack = attack * sampleRate + 9; // minimum attack to prevent pop
396
+ decay *= sampleRate;
397
+ sustain *= sampleRate;
398
+ release *= sampleRate;
399
+ delay *= sampleRate;
400
+ deltaSlide *= 500 * PI2 / sampleRate**3;
401
+ modulation *= PI2 / sampleRate;
402
+ pitchJump *= PI2 / sampleRate;
403
+ pitchJumpTime *= sampleRate;
404
+ repeatTime = repeatTime * sampleRate | 0;
405
+ volume *= soundVolume;
394
406
 
395
407
  // generate waveform
396
- for (length = attack + decay + sustain + release + delay | 0;
397
- i < length; b[i++] = s)
408
+ for(length = attack + decay + sustain + release + delay | 0;
409
+ i < length; b[i++] = s * volume) // sample
398
410
  {
399
- if (!(++c%(bitCrush*100|0))) // bit crush
411
+ if (!(++c%(bitCrush*100|0))) // bit crush
400
412
  {
401
- s = shape? shape>1? shape>2? shape>3? // wave shape
402
- Math.sin((t%PI2)**3) : // 4 noise
403
- max(min(Math.tan(t),1),-1): // 3 tan
404
- 1-(2*t/PI2%2+2)%2: // 2 saw
405
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
406
- Math.sin(t); // 0 sin
407
-
413
+ s = shape? shape>1? shape>2? shape>3? // wave shape
414
+ Math.sin(t*t) : // 4 noise
415
+ clamp(Math.tan(t),1,-1): // 3 tan
416
+ 1-(2*t/PI2%2+2)%2: // 2 saw
417
+ 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
418
+ Math.sin(t); // 0 sin
419
+
408
420
  s = (repeatTime ?
409
421
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
410
422
  : 1) *
411
- sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
412
- volume * soundVolume * ( // envelope
413
- i < attack ? i/attack : // attack
414
- i < attack + decay ? // decay
415
- 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
416
- i < attack + decay + sustain ? // sustain
417
- sustainVolume : // sustain volume
418
- i < length - delay ? // release
419
- (length - i - delay)/release * // release falloff
420
- sustainVolume : // release volume
421
- 0); // post release
422
-
423
- s = delay ? s/2 + (delay > i ? 0 : // delay
424
- (i<length-delay? 1 : (length-i)/delay) * // release delay
425
- b[i-delay|0]/2) : s; // sample delay
423
+ sign(s)*(abs(s)**shapeCurve) * // curve
424
+ (i < attack ? i/attack : // attack
425
+ i < attack + decay ? // decay
426
+ 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
427
+ i < attack + decay + sustain ? // sustain
428
+ sustainVolume : // sustain volume
429
+ i < length - delay ? // release
430
+ (length - i - delay)/release * // release falloff
431
+ sustainVolume : // release volume
432
+ 0); // post release
433
+
434
+ s = delay ? s/2 + (delay > i ? 0 : // delay
435
+ (i<length-delay? 1 : (length-i)/delay) * // release delay
436
+ b[i-delay|0]/2/volume) : s; // sample delay
437
+
438
+ if (filter) // apply filter
439
+ s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
426
440
  }
427
441
 
428
- f = (frequency += slide += deltaSlide) * // frequency
429
- Math.cos(modulation*tm++); // modulation
430
- t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
431
-
432
- if (j && ++j > pitchJumpTime) // pitch jump
433
- {
434
- frequency += pitchJump; // apply pitch jump
435
- startFrequency += pitchJump; // also apply to start
436
- j = 0; // reset pitch jump time
437
- }
438
-
439
- if (repeatTime && !(++r % repeatTime)) // repeat
440
- {
441
- frequency = startFrequency; // reset frequency
442
- slide = startSlide; // reset slide
443
- j ||= 1; // reset pitch jump time
442
+ f = (frequency += slide += deltaSlide) *// frequency
443
+ Math.cos(modulation*tm++); // modulation
444
+ t += f + f*noise*Math.sin(i**5); // noise
445
+
446
+ if (j && ++j > pitchJumpTime) // pitch jump
447
+ {
448
+ frequency += pitchJump; // apply pitch jump
449
+ startFrequency += pitchJump; // also apply to start
450
+ j = 0; // stop pitch jump time
451
+ }
452
+
453
+ if (repeatTime && !(++r % repeatTime)) // repeat
454
+ {
455
+ frequency = startFrequency; // reset frequency
456
+ slide = startSlide; // reset slide
457
+ j = j || 1; // reset pitch jump time
444
458
  }
445
459
  }
446
-
460
+
447
461
  return b;
448
462
  }
449
463