littlejsengine 1.18.29 → 1.20.0
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.
- package/FAQ.md +841 -815
- package/README.md +211 -199
- package/dist/littlejs.d.ts +4383 -812
- package/dist/littlejs.esm.js +31848 -16680
- package/dist/littlejs.esm.min.js +14 -1
- package/dist/littlejs.js +31230 -16166
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +27419 -15457
- package/package.json +10 -5
- package/plugins/audioEffects.js +375 -0
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -330
- package/plugins/math3d.js +911 -0
- package/plugins/medalSystem.js +118 -72
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +196 -115
- package/plugins/postProcess.js +284 -166
- package/plugins/render3d.js +4072 -0
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +86 -48
- package/plugins/threejs.js +183 -158
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +646 -526
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +8 -15
- package/src/engine.js +345 -194
- package/src/engineAudio.js +1022 -777
- package/src/engineBuild.mjs +289 -253
- package/src/engineDebug.js +912 -735
- package/src/engineDraw.js +1774 -1510
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +419 -396
- package/src/engineInput.js +1462 -1293
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +1274 -1183
- package/src/engineObject.js +674 -551
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +808 -726
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +32 -13
- package/src/engineWebGL.js +1162 -941
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlejsengine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "LittleJS - Tiny and Fast HTML5 Game Engine",
|
|
5
5
|
"main": "dist/littlejs.esm.js",
|
|
6
6
|
"types": "dist/littlejs.d.ts",
|
|
7
7
|
"exports": {
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/littlejs.d.ts",
|
|
10
|
+
"production": "./dist/littlejs.esm.min.js",
|
|
11
|
+
"default": "./dist/littlejs.esm.js"
|
|
12
|
+
},
|
|
13
|
+
"./dist/*": "./dist/*",
|
|
14
|
+
"./package.json": "./package.json"
|
|
11
15
|
},
|
|
12
16
|
"repository": {
|
|
13
17
|
"type": "git",
|
|
@@ -33,6 +37,7 @@
|
|
|
33
37
|
"dist/",
|
|
34
38
|
"src/",
|
|
35
39
|
"plugins/",
|
|
40
|
+
"!plugins/box2d.wasm.*",
|
|
36
41
|
"COPYRIGHT.txt",
|
|
37
42
|
"FAQ.md"
|
|
38
43
|
],
|
|
@@ -43,7 +48,7 @@
|
|
|
43
48
|
"test": "node --test --import ./test/setup.mjs \"test/**/*.test.mjs\""
|
|
44
49
|
},
|
|
45
50
|
"engines": {
|
|
46
|
-
"node": ">=
|
|
51
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
47
52
|
},
|
|
48
53
|
"devDependencies": {
|
|
49
54
|
"bestzip": "^2.2.1",
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Audio Effects Plugin
|
|
3
|
+
* - Web Audio effects with a wet/dry mix: filter, reverb, delay, distortion, compressor
|
|
4
|
+
* - Route a sound through one with sound.output = effect
|
|
5
|
+
* - Route everything with setAudioMasterEffect(effect)
|
|
6
|
+
* - Chain effects with effect.connect(nextEffect)
|
|
7
|
+
* @namespace AudioEffects
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
// audioParamRamp lives in engineAudio.js, core loads before plugins in the bundle
|
|
14
|
+
/**
|
|
15
|
+
* Base class for audio effects, an input and output with a wet/dry mix between them
|
|
16
|
+
* - Sounds connect to input, output goes to the master gain until connect() moves it
|
|
17
|
+
* - Subclasses put their nodes between input and the wet gain with connectEffect
|
|
18
|
+
* @memberof AudioEffects
|
|
19
|
+
* @example
|
|
20
|
+
* const cave = new AudioReverb(3, 2);
|
|
21
|
+
* footstep.output = cave; // every play of this sound is in the cave
|
|
22
|
+
*/
|
|
23
|
+
class AudioEffect
|
|
24
|
+
{
|
|
25
|
+
/** Create an audio effect
|
|
26
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
27
|
+
constructor(mix=1)
|
|
28
|
+
{
|
|
29
|
+
ASSERT(isNumber(mix), 'mix must be a number');
|
|
30
|
+
|
|
31
|
+
/** @property {number} - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
32
|
+
this.mix = clamp(mix);
|
|
33
|
+
if (!audioContext) return; // no audio outside a browser, where the engine runs headless
|
|
34
|
+
|
|
35
|
+
/** @property {GainNode} - Connect sounds to this node */
|
|
36
|
+
this.input = audioContext.createGain();
|
|
37
|
+
/** @property {GainNode} - This node carries the mixed result, send it somewhere with connect(), never by assigning here
|
|
38
|
+
* - Unlike sound.output, which is where a sound's audio goes and can be set to an effect */
|
|
39
|
+
this.output = audioContext.createGain();
|
|
40
|
+
/** @property {GainNode} - Level of the unprocessed signal */
|
|
41
|
+
this.dryGain = audioContext.createGain();
|
|
42
|
+
/** @property {GainNode} - Level of the processed signal */
|
|
43
|
+
this.wetGain = audioContext.createGain();
|
|
44
|
+
|
|
45
|
+
this.input.connect(this.dryGain).connect(this.output);
|
|
46
|
+
this.wetGain.connect(this.output);
|
|
47
|
+
this.setMix(mix);
|
|
48
|
+
|
|
49
|
+
// send the result to the speakers, connect() moves it into a chain instead
|
|
50
|
+
this.output.connect(audioMasterGain);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Set the wet/dry balance
|
|
54
|
+
* @param {number} mix - 0 is fully dry and 1 is fully wet
|
|
55
|
+
* @param {number} [fadeTime] - Seconds to ramp over so the change doesn't click */
|
|
56
|
+
setMix(mix, fadeTime=0)
|
|
57
|
+
{
|
|
58
|
+
ASSERT(isNumber(mix), 'mix must be a number');
|
|
59
|
+
this.mix = mix = clamp(mix);
|
|
60
|
+
if (!audioContext) return;
|
|
61
|
+
this.rampParam(this.dryGain.gain, 1-mix, fadeTime);
|
|
62
|
+
this.rampParam(this.wetGain.gain, mix, fadeTime);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Ramp one of this effect's params, keeping the effect running until the ramp is done
|
|
66
|
+
* - The browser drops an effect from rendering while nothing plays through it, which
|
|
67
|
+
* would freeze a ramp partway, so a silent source feeds the input for the ramp's length
|
|
68
|
+
* @param {AudioParam} param - The param to ramp
|
|
69
|
+
* @param {number} value - Where to ramp to
|
|
70
|
+
* @param {number} [fadeTime] - Seconds to ramp over, 0 sets the value at once
|
|
71
|
+
* @protected */
|
|
72
|
+
rampParam(param, value, fadeTime=0)
|
|
73
|
+
{
|
|
74
|
+
audioParamRamp(param, value, fadeTime);
|
|
75
|
+
if (!fadeTime) return;
|
|
76
|
+
const keepAlive = new ConstantSourceNode(audioContext, { offset: 0 });
|
|
77
|
+
keepAlive.connect(this.input);
|
|
78
|
+
keepAlive.onended = ()=> keepAlive.disconnect();
|
|
79
|
+
keepAlive.start();
|
|
80
|
+
keepAlive.stop(audioContext.currentTime + fadeTime);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Send this effect's output into another effect or audio node instead of the speakers
|
|
84
|
+
* @template {AudioEffect|AudioNode} T
|
|
85
|
+
* @param {T} target - The next effect in the chain, or any audio node
|
|
86
|
+
* @return {T} - The target, so chains read left to right */
|
|
87
|
+
connect(target)
|
|
88
|
+
{
|
|
89
|
+
if (!audioContext) return target; // no audio outside a browser, where the engine runs headless
|
|
90
|
+
|
|
91
|
+
// an effect stands in for its input node, the same rule as sound.output
|
|
92
|
+
const node = /** @type {AudioNode} */ (target && 'input' in target ? target.input : target);
|
|
93
|
+
ASSERT(node && typeof node.connect === 'function', 'target must be an AudioEffect or AudioNode');
|
|
94
|
+
this.output.disconnect();
|
|
95
|
+
this.output.connect(node);
|
|
96
|
+
return target;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Stop sending this effect's output anywhere */
|
|
100
|
+
disconnect() { this.output?.disconnect(); }
|
|
101
|
+
|
|
102
|
+
/** Wire nodes between the input and the wet gain, for subclasses
|
|
103
|
+
* @param {AudioNode} first - Node the input connects to
|
|
104
|
+
* @param {AudioNode} [last=first] - Node that connects to the wet gain
|
|
105
|
+
* @protected */
|
|
106
|
+
connectEffect(first, last=first)
|
|
107
|
+
{
|
|
108
|
+
this.input.connect(first);
|
|
109
|
+
last.connect(this.wetGain);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
114
|
+
/**
|
|
115
|
+
* Filter effect, muffle sounds underwater or behind a wall
|
|
116
|
+
* @extends AudioEffect
|
|
117
|
+
* @memberof AudioEffects
|
|
118
|
+
* @example
|
|
119
|
+
* const muffle = new AudioFilter('lowpass', 400);
|
|
120
|
+
* setAudioMasterEffect(muffle);
|
|
121
|
+
* muffle.setFrequency(20000, .5); // sweep back to clear
|
|
122
|
+
*/
|
|
123
|
+
class AudioFilter extends AudioEffect
|
|
124
|
+
{
|
|
125
|
+
/** Create a filter effect
|
|
126
|
+
* @param {BiquadFilterType} [type] - lowpass, highpass, bandpass, notch, etc.
|
|
127
|
+
* @param {number} [frequency] - Cutoff or center frequency in Hz
|
|
128
|
+
* @param {number} [q] - Resonance at the cutoff, higher is sharper
|
|
129
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
130
|
+
constructor(type='lowpass', frequency=1000, q=1, mix=1)
|
|
131
|
+
{
|
|
132
|
+
super(mix);
|
|
133
|
+
ASSERT(isNumber(frequency) && frequency >= 0, 'frequency must be positive or zero');
|
|
134
|
+
ASSERT(isNumber(q), 'q must be a number');
|
|
135
|
+
if (!audioContext) return; // no audio outside a browser, where the engine runs headless
|
|
136
|
+
|
|
137
|
+
/** @property {BiquadFilterNode} - The filter node */
|
|
138
|
+
this.node = audioContext.createBiquadFilter();
|
|
139
|
+
this.node.type = type;
|
|
140
|
+
this.node.frequency.value = frequency;
|
|
141
|
+
this.node.Q.value = q;
|
|
142
|
+
this.connectEffect(this.node);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Set the cutoff or center frequency
|
|
146
|
+
* @param {number} frequency - Frequency in Hz
|
|
147
|
+
* @param {number} [fadeTime] - Seconds to sweep over */
|
|
148
|
+
setFrequency(frequency, fadeTime=0)
|
|
149
|
+
{
|
|
150
|
+
ASSERT(isNumber(frequency) && frequency >= 0, 'frequency must be positive or zero');
|
|
151
|
+
if (!audioContext) return;
|
|
152
|
+
this.rampParam(this.node.frequency, frequency, fadeTime);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Set the resonance at the cutoff
|
|
156
|
+
* @param {number} q - Higher is sharper
|
|
157
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
158
|
+
setQ(q, fadeTime=0)
|
|
159
|
+
{
|
|
160
|
+
ASSERT(isNumber(q), 'q must be a number');
|
|
161
|
+
if (!audioContext) return;
|
|
162
|
+
this.rampParam(this.node.Q, q, fadeTime);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
167
|
+
/**
|
|
168
|
+
* Reverb effect, puts sounds in a room, cave, or hall
|
|
169
|
+
* - The impulse response is generated, no audio file needed
|
|
170
|
+
* @extends AudioEffect
|
|
171
|
+
* @memberof AudioEffects
|
|
172
|
+
* @example
|
|
173
|
+
* const hall = new AudioReverb(4, 1.5, .4);
|
|
174
|
+
* footstep.output = hall;
|
|
175
|
+
*/
|
|
176
|
+
class AudioReverb extends AudioEffect
|
|
177
|
+
{
|
|
178
|
+
/** Create a reverb effect
|
|
179
|
+
* @param {number} [duration] - Seconds until the reverb tail is silent
|
|
180
|
+
* @param {number} [decay] - How quickly the tail fades, higher is faster
|
|
181
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
182
|
+
constructor(duration=2, decay=2, mix=.5)
|
|
183
|
+
{
|
|
184
|
+
super(mix);
|
|
185
|
+
if (!audioContext) return; // no audio outside a browser, where the engine runs headless
|
|
186
|
+
|
|
187
|
+
/** @property {ConvolverNode} - The convolver node */
|
|
188
|
+
this.node = audioContext.createConvolver();
|
|
189
|
+
this.setRoom(duration, decay);
|
|
190
|
+
this.connectEffect(this.node);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Change the room by rebuilding the impulse response
|
|
194
|
+
* @param {number} duration - Seconds until the reverb tail is silent
|
|
195
|
+
* @param {number} [decay] - How quickly the tail fades, higher is faster */
|
|
196
|
+
setRoom(duration, decay=2)
|
|
197
|
+
{
|
|
198
|
+
ASSERT(isNumber(duration) && duration > 0, 'duration must be positive');
|
|
199
|
+
ASSERT(isNumber(decay) && decay > 0, 'decay must be positive');
|
|
200
|
+
if (!audioContext) return;
|
|
201
|
+
this.node.buffer = this.createImpulse(duration, decay);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Build a stereo impulse response of decaying noise
|
|
205
|
+
* @param {number} duration - Seconds until silence
|
|
206
|
+
* @param {number} decay - How quickly it fades, higher is faster
|
|
207
|
+
* @return {AudioBuffer} */
|
|
208
|
+
createImpulse(duration, decay)
|
|
209
|
+
{
|
|
210
|
+
const sampleRate = audioContext.sampleRate;
|
|
211
|
+
const length = max(1, sampleRate * duration | 0);
|
|
212
|
+
const buffer = audioContext.createBuffer(2, length, sampleRate);
|
|
213
|
+
for (let channel = 2; channel--;)
|
|
214
|
+
{
|
|
215
|
+
const samples = buffer.getChannelData(channel);
|
|
216
|
+
for (let i = length; i--;)
|
|
217
|
+
samples[i] = rand(-1, 1) * (1 - i/length) ** decay;
|
|
218
|
+
}
|
|
219
|
+
return buffer;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
224
|
+
/**
|
|
225
|
+
* Delay effect, echoes that repeat and fade
|
|
226
|
+
* @extends AudioEffect
|
|
227
|
+
* @memberof AudioEffects
|
|
228
|
+
* @example
|
|
229
|
+
* const canyon = new AudioDelay(.4, .5);
|
|
230
|
+
* shout.output = canyon;
|
|
231
|
+
*/
|
|
232
|
+
class AudioDelay extends AudioEffect
|
|
233
|
+
{
|
|
234
|
+
/** Create a delay effect
|
|
235
|
+
* @param {number} [time] - Seconds between echoes, up to 5
|
|
236
|
+
* @param {number} [feedback] - How much of each echo repeats, 0 to .95
|
|
237
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
238
|
+
constructor(time=.3, feedback=.4, mix=.5)
|
|
239
|
+
{
|
|
240
|
+
super(mix);
|
|
241
|
+
if (!audioContext) return; // no audio outside a browser, where the engine runs headless
|
|
242
|
+
|
|
243
|
+
/** @property {DelayNode} - The delay node */
|
|
244
|
+
this.node = audioContext.createDelay(5);
|
|
245
|
+
/** @property {GainNode} - How much of the delayed signal feeds back in */
|
|
246
|
+
this.feedbackGain = audioContext.createGain();
|
|
247
|
+
this.node.connect(this.feedbackGain).connect(this.node);
|
|
248
|
+
this.connectEffect(this.node);
|
|
249
|
+
this.setTime(time);
|
|
250
|
+
this.setFeedback(feedback);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Set the time between echoes
|
|
254
|
+
* - Browsers hold a delay in a feedback loop to at least one render quantum, so 0 is not a bypass
|
|
255
|
+
* @param {number} time - Seconds, up to 5
|
|
256
|
+
* @param {number} [fadeTime] - Seconds to ramp over, pitch bends while it moves */
|
|
257
|
+
setTime(time, fadeTime=0)
|
|
258
|
+
{
|
|
259
|
+
ASSERT(isNumber(time) && time >= 0 && time <= 5, 'time must be between 0 and 5');
|
|
260
|
+
if (!audioContext) return;
|
|
261
|
+
this.rampParam(this.node.delayTime, time, fadeTime);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Set how much of each echo repeats, clamped below 1 so it always dies out
|
|
265
|
+
* @param {number} feedback - 0 to .95
|
|
266
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
267
|
+
setFeedback(feedback, fadeTime=0)
|
|
268
|
+
{
|
|
269
|
+
ASSERT(isNumber(feedback), 'feedback must be a number');
|
|
270
|
+
if (!audioContext) return;
|
|
271
|
+
this.rampParam(this.feedbackGain.gain, clamp(feedback, 0, .95), fadeTime);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
276
|
+
/**
|
|
277
|
+
* Distortion effect, overdrive for radios, damaged robots, and engines
|
|
278
|
+
* @extends AudioEffect
|
|
279
|
+
* @memberof AudioEffects
|
|
280
|
+
* @example
|
|
281
|
+
* const radio = new AudioDistortion(.8);
|
|
282
|
+
* voice.output = radio;
|
|
283
|
+
*/
|
|
284
|
+
class AudioDistortion extends AudioEffect
|
|
285
|
+
{
|
|
286
|
+
/** Create a distortion effect
|
|
287
|
+
* @param {number} [amount] - How hard to drive the signal, 0 is clean and 1 is crushed
|
|
288
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
289
|
+
constructor(amount=.5, mix=1)
|
|
290
|
+
{
|
|
291
|
+
super(mix);
|
|
292
|
+
|
|
293
|
+
/** @property {number} - How hard the signal is driven, 0 is clean and 1 is crushed */
|
|
294
|
+
this.amount = clamp(amount);
|
|
295
|
+
if (!audioContext) return; // no audio outside a browser, where the engine runs headless
|
|
296
|
+
|
|
297
|
+
/** @property {WaveShaperNode} - The wave shaper node */
|
|
298
|
+
this.node = audioContext.createWaveShaper();
|
|
299
|
+
this.node.oversample = '2x';
|
|
300
|
+
this.setAmount(amount);
|
|
301
|
+
this.connectEffect(this.node);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Set how hard to drive the signal, rebuilds the shaping curve
|
|
305
|
+
* @param {number} amount - 0 is clean and 1 is crushed */
|
|
306
|
+
setAmount(amount)
|
|
307
|
+
{
|
|
308
|
+
ASSERT(isNumber(amount), 'amount must be a number');
|
|
309
|
+
this.amount = amount = clamp(amount);
|
|
310
|
+
if (!audioContext) return;
|
|
311
|
+
|
|
312
|
+
// soft clip curve, drive grows with the square of amount so low values stay subtle
|
|
313
|
+
// enough points that quiet signals are still shaped at high drive, where the curve is steep near 0
|
|
314
|
+
const drive = 100 * amount * amount;
|
|
315
|
+
const samples = 1024;
|
|
316
|
+
const curve = new Float32Array(samples);
|
|
317
|
+
for (let i = samples; i--;)
|
|
318
|
+
{
|
|
319
|
+
const x = i * 2 / (samples - 1) - 1;
|
|
320
|
+
curve[i] = (1 + drive) * x / (1 + drive * abs(x));
|
|
321
|
+
}
|
|
322
|
+
this.node.curve = curve;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
327
|
+
/**
|
|
328
|
+
* Compressor effect, evens out loud and quiet so many sounds at once don't clip
|
|
329
|
+
* - Meant for the master bus, it is not on by default
|
|
330
|
+
* @extends AudioEffect
|
|
331
|
+
* @memberof AudioEffects
|
|
332
|
+
* @example
|
|
333
|
+
* const compressor = new AudioCompressor;
|
|
334
|
+
* setAudioMasterEffect(compressor);
|
|
335
|
+
*/
|
|
336
|
+
class AudioCompressor extends AudioEffect
|
|
337
|
+
{
|
|
338
|
+
/** Create a compressor effect
|
|
339
|
+
* @param {number} [threshold] - Level in dB above which the signal is reduced
|
|
340
|
+
* @param {number} [ratio] - How much to reduce it, 12 means 12 dB in becomes 1 dB out
|
|
341
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
342
|
+
constructor(threshold=-24, ratio=12, mix=1)
|
|
343
|
+
{
|
|
344
|
+
super(mix);
|
|
345
|
+
ASSERT(isNumber(threshold), 'threshold must be a number');
|
|
346
|
+
ASSERT(isNumber(ratio) && ratio >= 1, 'ratio must be 1 or more');
|
|
347
|
+
if (!audioContext) return; // no audio outside a browser, where the engine runs headless
|
|
348
|
+
|
|
349
|
+
/** @property {DynamicsCompressorNode} - The compressor node */
|
|
350
|
+
this.node = audioContext.createDynamicsCompressor();
|
|
351
|
+
this.node.threshold.value = threshold;
|
|
352
|
+
this.node.ratio.value = ratio;
|
|
353
|
+
this.connectEffect(this.node);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Set the level above which the signal is reduced
|
|
357
|
+
* @param {number} threshold - Level in dB
|
|
358
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
359
|
+
setThreshold(threshold, fadeTime=0)
|
|
360
|
+
{
|
|
361
|
+
ASSERT(isNumber(threshold), 'threshold must be a number');
|
|
362
|
+
if (!audioContext) return;
|
|
363
|
+
this.rampParam(this.node.threshold, threshold, fadeTime);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Set how much the signal is reduced above the threshold
|
|
367
|
+
* @param {number} ratio - 1 is no reduction, 20 is a hard limit
|
|
368
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
369
|
+
setRatio(ratio, fadeTime=0)
|
|
370
|
+
{
|
|
371
|
+
ASSERT(isNumber(ratio) && ratio >= 1, 'ratio must be 1 or more');
|
|
372
|
+
if (!audioContext) return;
|
|
373
|
+
this.rampParam(this.node.ratio, ratio, fadeTime);
|
|
374
|
+
}
|
|
375
|
+
}
|