littlejsengine 1.18.27 → 1.18.28
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/dist/littlejs.d.ts +39 -2
- package/dist/littlejs.esm.js +106 -40
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +104 -40
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +104 -40
- package/package.json +1 -1
- package/src/engine.js +1 -1
- package/src/engineAudio.js +103 -39
- package/src/engineExport.js +2 -0
package/dist/littlejs.release.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.18.
|
|
38
|
+
const engineVersion = '1.18.28';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -6283,7 +6283,15 @@ class Sound
|
|
|
6283
6283
|
this.randomness = randomness ?? 0;
|
|
6284
6284
|
/** @property {number} - Sample rate for this sound */
|
|
6285
6285
|
this.sampleRate = audioDefaultSampleRate;
|
|
6286
|
-
/** @property {number} -
|
|
6286
|
+
/** @property {number} - How many samples per channel this sound has */
|
|
6287
|
+
this.sampleLength = 0;
|
|
6288
|
+
/** @property {AudioBuffer} - Decoded audio shared by every play of this sound
|
|
6289
|
+
* @type {AudioBuffer} */
|
|
6290
|
+
this.sampleBuffer = undefined;
|
|
6291
|
+
/** @private @type {Array<Array<number>|Float32Array>} */
|
|
6292
|
+
this._sampleChannels = undefined;
|
|
6293
|
+
/** @property {number} - Percentage of this sound currently loaded, sounds
|
|
6294
|
+
* fetched from a url stay at 0 until decoding completes */
|
|
6287
6295
|
this.loadedPercent = 0;
|
|
6288
6296
|
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
6289
6297
|
this.onloadCallback = onloadCallback;
|
|
@@ -6299,8 +6307,10 @@ class Sound
|
|
|
6299
6307
|
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
6300
6308
|
zzfxSound[randomnessIndex] = 0;
|
|
6301
6309
|
|
|
6302
|
-
// generate the zzfx samples
|
|
6310
|
+
// generate the zzfx samples, then hand them to an audio buffer so
|
|
6311
|
+
// the plain arrays can be released and every play shares the buffer
|
|
6303
6312
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
6313
|
+
this.buildSampleBuffer();
|
|
6304
6314
|
this.loadedPercent = 1;
|
|
6305
6315
|
onloadCallback?.(this);
|
|
6306
6316
|
}
|
|
@@ -6312,6 +6322,45 @@ class Sound
|
|
|
6312
6322
|
}
|
|
6313
6323
|
}
|
|
6314
6324
|
|
|
6325
|
+
/** Sample data for each channel
|
|
6326
|
+
* Sounds keep their samples in an audio buffer, so reading this rebuilds
|
|
6327
|
+
* the arrays from it and caches them. The copies are safe to hold onto,
|
|
6328
|
+
* playing a sound detaches the buffer's own channel arrays.
|
|
6329
|
+
* @type {Array<Array<number>|Float32Array>} */
|
|
6330
|
+
get sampleChannels()
|
|
6331
|
+
{
|
|
6332
|
+
const buffer = this.sampleBuffer;
|
|
6333
|
+
if (!this._sampleChannels && buffer)
|
|
6334
|
+
{
|
|
6335
|
+
const channels = [];
|
|
6336
|
+
for (let i = 0; i < buffer.numberOfChannels; i++)
|
|
6337
|
+
channels.push(buffer.getChannelData(i).slice());
|
|
6338
|
+
this._sampleChannels = channels;
|
|
6339
|
+
}
|
|
6340
|
+
return this._sampleChannels;
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
/** @param {Array<Array<number>|Float32Array>} sampleChannels */
|
|
6344
|
+
set sampleChannels(sampleChannels)
|
|
6345
|
+
{
|
|
6346
|
+
// new samples invalidate the buffer built from the old ones
|
|
6347
|
+
this._sampleChannels = sampleChannels;
|
|
6348
|
+
this.sampleBuffer = undefined;
|
|
6349
|
+
this.sampleLength = sampleChannels?.[0]?.length || 0;
|
|
6350
|
+
}
|
|
6351
|
+
|
|
6352
|
+
/** Move this sound's samples into an audio buffer that every play can share
|
|
6353
|
+
* Does nothing if there is already a buffer or no samples to build one from */
|
|
6354
|
+
buildSampleBuffer()
|
|
6355
|
+
{
|
|
6356
|
+
if (this.sampleBuffer || !this._sampleChannels || headlessMode) return;
|
|
6357
|
+
|
|
6358
|
+
this.sampleBuffer = createAudioBuffer(this._sampleChannels, this.sampleRate);
|
|
6359
|
+
|
|
6360
|
+
// the buffer owns the samples now, release the arrays we built it from
|
|
6361
|
+
this._sampleChannels = undefined;
|
|
6362
|
+
}
|
|
6363
|
+
|
|
6315
6364
|
/** Play the sound
|
|
6316
6365
|
* Sounds may not play until a user interaction occurs
|
|
6317
6366
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
@@ -6330,7 +6379,7 @@ class Sound
|
|
|
6330
6379
|
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
6331
6380
|
|
|
6332
6381
|
if (!soundEnable || headlessMode) return;
|
|
6333
|
-
if (!this.
|
|
6382
|
+
if (!this.sampleBuffer && !this._sampleChannels) return;
|
|
6334
6383
|
|
|
6335
6384
|
let pan;
|
|
6336
6385
|
if (pos)
|
|
@@ -6397,7 +6446,7 @@ class Sound
|
|
|
6397
6446
|
* @return {number} - How long the sound is in seconds (0 if loading)
|
|
6398
6447
|
*/
|
|
6399
6448
|
getDuration()
|
|
6400
|
-
{ return this.
|
|
6449
|
+
{ return this.sampleLength / this.sampleRate || 0; }
|
|
6401
6450
|
|
|
6402
6451
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
6403
6452
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -6415,36 +6464,11 @@ class Sound
|
|
|
6415
6464
|
const arrayBuffer = await response.arrayBuffer();
|
|
6416
6465
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
6417
6466
|
|
|
6418
|
-
//
|
|
6419
|
-
|
|
6420
|
-
const samplesPerFrame = 1e5;
|
|
6421
|
-
const sampleChannels = [];
|
|
6422
|
-
for (let channel = 0; channel < channelCount; channel++)
|
|
6423
|
-
{
|
|
6424
|
-
const channelData = audioBuffer.getChannelData(channel);
|
|
6425
|
-
const channelLength = channelData.length;
|
|
6426
|
-
sampleChannels[channel] = new Array(channelLength);
|
|
6427
|
-
let sampleIndex = 0;
|
|
6428
|
-
while (sampleIndex < channelLength)
|
|
6429
|
-
{
|
|
6430
|
-
// yield to next frame
|
|
6431
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
6432
|
-
|
|
6433
|
-
// copy chunk of samples
|
|
6434
|
-
const endIndex = min(sampleIndex + samplesPerFrame, channelLength);
|
|
6435
|
-
for (; sampleIndex < endIndex; sampleIndex++)
|
|
6436
|
-
sampleChannels[channel][sampleIndex] = channelData[sampleIndex];
|
|
6437
|
-
|
|
6438
|
-
// update loaded percent
|
|
6439
|
-
const samplesTotal = channelCount * channelLength;
|
|
6440
|
-
const samplesProcessed = channel * channelLength + sampleIndex;
|
|
6441
|
-
this.loadedPercent = samplesProcessed / samplesTotal;
|
|
6442
|
-
}
|
|
6443
|
-
}
|
|
6444
|
-
|
|
6445
|
-
// setup the sound to be played
|
|
6467
|
+
// keep the decoded buffer as is, it is exactly what playback needs and
|
|
6468
|
+
// every play shares it, no channel data is read or copied
|
|
6446
6469
|
this.sampleRate = audioBuffer.sampleRate;
|
|
6447
|
-
this.
|
|
6470
|
+
this.sampleLength = audioBuffer.length;
|
|
6471
|
+
this.sampleBuffer = audioBuffer;
|
|
6448
6472
|
this.loadedPercent = 1;
|
|
6449
6473
|
this.onloadCallback?.(this);
|
|
6450
6474
|
}
|
|
@@ -6520,7 +6544,12 @@ class SoundInstance
|
|
|
6520
6544
|
if (this.isPlaying())
|
|
6521
6545
|
this.stop();
|
|
6522
6546
|
this.gainNode = audioContext.createGain();
|
|
6523
|
-
|
|
6547
|
+
|
|
6548
|
+
// build the shared buffer if it was not made at load time, then play it
|
|
6549
|
+
this.sound.buildSampleBuffer();
|
|
6550
|
+
this.source = this.sound.sampleBuffer ?
|
|
6551
|
+
playAudioBuffer(this.sound.sampleBuffer, this.volume, this.rate, this.pan, this.loop, this.gainNode, offset, this.onendedCallback) :
|
|
6552
|
+
playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
6524
6553
|
if (this.source)
|
|
6525
6554
|
{
|
|
6526
6555
|
this.startTime = audioContext.currentTime - offset;
|
|
@@ -6695,19 +6724,54 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
6695
6724
|
|
|
6696
6725
|
if (!audioIsRunning())
|
|
6697
6726
|
{
|
|
6698
|
-
// fix stalled audio,
|
|
6727
|
+
// fix stalled audio, don't build a buffer that can't be played
|
|
6699
6728
|
audioContext.resume();
|
|
6700
6729
|
return;
|
|
6701
6730
|
}
|
|
6702
6731
|
|
|
6703
|
-
|
|
6732
|
+
const buffer = createAudioBuffer(sampleChannels, sampleRate);
|
|
6733
|
+
return playAudioBuffer(buffer, volume, rate, pan, loop, gainNode, offset, onended);
|
|
6734
|
+
}
|
|
6735
|
+
|
|
6736
|
+
/** Copy arrays of samples into a new audio buffer
|
|
6737
|
+
* @param {Array} sampleChannels - Array of arrays of samples (for stereo playback)
|
|
6738
|
+
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
6739
|
+
* @return {AudioBuffer} - The audio buffer holding the samples
|
|
6740
|
+
* @memberof Audio */
|
|
6741
|
+
function createAudioBuffer(sampleChannels, sampleRate=audioDefaultSampleRate)
|
|
6742
|
+
{
|
|
6704
6743
|
const channelCount = sampleChannels.length;
|
|
6705
6744
|
const sampleLength = sampleChannels[0].length;
|
|
6706
6745
|
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
6707
|
-
const source = audioContext.createBufferSource();
|
|
6708
|
-
|
|
6709
|
-
// copy samples to buffer and setup source
|
|
6710
6746
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
6747
|
+
return buffer;
|
|
6748
|
+
}
|
|
6749
|
+
|
|
6750
|
+
/** Play an audio buffer with given settings
|
|
6751
|
+
* The buffer can be shared by any number of sounds playing at once
|
|
6752
|
+
* @param {AudioBuffer} buffer - The audio buffer to play
|
|
6753
|
+
* @param {number} [volume] - How much to scale volume by
|
|
6754
|
+
* @param {number} [rate] - The playback rate to use
|
|
6755
|
+
* @param {number} [pan] - How much to apply stereo panning
|
|
6756
|
+
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
6757
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
6758
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
6759
|
+
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
6760
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
6761
|
+
* @memberof Audio */
|
|
6762
|
+
function playAudioBuffer(buffer, volume=1, rate=1, pan=0, loop=false, gainNode, offset=0, onended)
|
|
6763
|
+
{
|
|
6764
|
+
if (!soundEnable || headlessMode) return;
|
|
6765
|
+
|
|
6766
|
+
if (!audioIsRunning())
|
|
6767
|
+
{
|
|
6768
|
+
// fix stalled audio, this sound won't be able to play
|
|
6769
|
+
audioContext.resume();
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
// setup source, many sources can share one buffer
|
|
6774
|
+
const source = audioContext.createBufferSource();
|
|
6711
6775
|
source.buffer = buffer;
|
|
6712
6776
|
source.playbackRate.value = rate;
|
|
6713
6777
|
source.loop = loop;
|
package/package.json
CHANGED
package/src/engine.js
CHANGED
package/src/engineAudio.js
CHANGED
|
@@ -98,7 +98,15 @@ class Sound
|
|
|
98
98
|
this.randomness = randomness ?? 0;
|
|
99
99
|
/** @property {number} - Sample rate for this sound */
|
|
100
100
|
this.sampleRate = audioDefaultSampleRate;
|
|
101
|
-
/** @property {number} -
|
|
101
|
+
/** @property {number} - How many samples per channel this sound has */
|
|
102
|
+
this.sampleLength = 0;
|
|
103
|
+
/** @property {AudioBuffer} - Decoded audio shared by every play of this sound
|
|
104
|
+
* @type {AudioBuffer} */
|
|
105
|
+
this.sampleBuffer = undefined;
|
|
106
|
+
/** @private @type {Array<Array<number>|Float32Array>} */
|
|
107
|
+
this._sampleChannels = undefined;
|
|
108
|
+
/** @property {number} - Percentage of this sound currently loaded, sounds
|
|
109
|
+
* fetched from a url stay at 0 until decoding completes */
|
|
102
110
|
this.loadedPercent = 0;
|
|
103
111
|
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
104
112
|
this.onloadCallback = onloadCallback;
|
|
@@ -114,8 +122,10 @@ class Sound
|
|
|
114
122
|
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
115
123
|
zzfxSound[randomnessIndex] = 0;
|
|
116
124
|
|
|
117
|
-
// generate the zzfx samples
|
|
125
|
+
// generate the zzfx samples, then hand them to an audio buffer so
|
|
126
|
+
// the plain arrays can be released and every play shares the buffer
|
|
118
127
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
128
|
+
this.buildSampleBuffer();
|
|
119
129
|
this.loadedPercent = 1;
|
|
120
130
|
onloadCallback?.(this);
|
|
121
131
|
}
|
|
@@ -127,6 +137,45 @@ class Sound
|
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
/** Sample data for each channel
|
|
141
|
+
* Sounds keep their samples in an audio buffer, so reading this rebuilds
|
|
142
|
+
* the arrays from it and caches them. The copies are safe to hold onto,
|
|
143
|
+
* playing a sound detaches the buffer's own channel arrays.
|
|
144
|
+
* @type {Array<Array<number>|Float32Array>} */
|
|
145
|
+
get sampleChannels()
|
|
146
|
+
{
|
|
147
|
+
const buffer = this.sampleBuffer;
|
|
148
|
+
if (!this._sampleChannels && buffer)
|
|
149
|
+
{
|
|
150
|
+
const channels = [];
|
|
151
|
+
for (let i = 0; i < buffer.numberOfChannels; i++)
|
|
152
|
+
channels.push(buffer.getChannelData(i).slice());
|
|
153
|
+
this._sampleChannels = channels;
|
|
154
|
+
}
|
|
155
|
+
return this._sampleChannels;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @param {Array<Array<number>|Float32Array>} sampleChannels */
|
|
159
|
+
set sampleChannels(sampleChannels)
|
|
160
|
+
{
|
|
161
|
+
// new samples invalidate the buffer built from the old ones
|
|
162
|
+
this._sampleChannels = sampleChannels;
|
|
163
|
+
this.sampleBuffer = undefined;
|
|
164
|
+
this.sampleLength = sampleChannels?.[0]?.length || 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Move this sound's samples into an audio buffer that every play can share
|
|
168
|
+
* Does nothing if there is already a buffer or no samples to build one from */
|
|
169
|
+
buildSampleBuffer()
|
|
170
|
+
{
|
|
171
|
+
if (this.sampleBuffer || !this._sampleChannels || headlessMode) return;
|
|
172
|
+
|
|
173
|
+
this.sampleBuffer = createAudioBuffer(this._sampleChannels, this.sampleRate);
|
|
174
|
+
|
|
175
|
+
// the buffer owns the samples now, release the arrays we built it from
|
|
176
|
+
this._sampleChannels = undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
130
179
|
/** Play the sound
|
|
131
180
|
* Sounds may not play until a user interaction occurs
|
|
132
181
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
@@ -145,7 +194,7 @@ class Sound
|
|
|
145
194
|
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
146
195
|
|
|
147
196
|
if (!soundEnable || headlessMode) return;
|
|
148
|
-
if (!this.
|
|
197
|
+
if (!this.sampleBuffer && !this._sampleChannels) return;
|
|
149
198
|
|
|
150
199
|
let pan;
|
|
151
200
|
if (pos)
|
|
@@ -212,7 +261,7 @@ class Sound
|
|
|
212
261
|
* @return {number} - How long the sound is in seconds (0 if loading)
|
|
213
262
|
*/
|
|
214
263
|
getDuration()
|
|
215
|
-
{ return this.
|
|
264
|
+
{ return this.sampleLength / this.sampleRate || 0; }
|
|
216
265
|
|
|
217
266
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
218
267
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -230,36 +279,11 @@ class Sound
|
|
|
230
279
|
const arrayBuffer = await response.arrayBuffer();
|
|
231
280
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
232
281
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
const samplesPerFrame = 1e5;
|
|
236
|
-
const sampleChannels = [];
|
|
237
|
-
for (let channel = 0; channel < channelCount; channel++)
|
|
238
|
-
{
|
|
239
|
-
const channelData = audioBuffer.getChannelData(channel);
|
|
240
|
-
const channelLength = channelData.length;
|
|
241
|
-
sampleChannels[channel] = new Array(channelLength);
|
|
242
|
-
let sampleIndex = 0;
|
|
243
|
-
while (sampleIndex < channelLength)
|
|
244
|
-
{
|
|
245
|
-
// yield to next frame
|
|
246
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
247
|
-
|
|
248
|
-
// copy chunk of samples
|
|
249
|
-
const endIndex = min(sampleIndex + samplesPerFrame, channelLength);
|
|
250
|
-
for (; sampleIndex < endIndex; sampleIndex++)
|
|
251
|
-
sampleChannels[channel][sampleIndex] = channelData[sampleIndex];
|
|
252
|
-
|
|
253
|
-
// update loaded percent
|
|
254
|
-
const samplesTotal = channelCount * channelLength;
|
|
255
|
-
const samplesProcessed = channel * channelLength + sampleIndex;
|
|
256
|
-
this.loadedPercent = samplesProcessed / samplesTotal;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// setup the sound to be played
|
|
282
|
+
// keep the decoded buffer as is, it is exactly what playback needs and
|
|
283
|
+
// every play shares it, no channel data is read or copied
|
|
261
284
|
this.sampleRate = audioBuffer.sampleRate;
|
|
262
|
-
this.
|
|
285
|
+
this.sampleLength = audioBuffer.length;
|
|
286
|
+
this.sampleBuffer = audioBuffer;
|
|
263
287
|
this.loadedPercent = 1;
|
|
264
288
|
this.onloadCallback?.(this);
|
|
265
289
|
}
|
|
@@ -335,7 +359,12 @@ class SoundInstance
|
|
|
335
359
|
if (this.isPlaying())
|
|
336
360
|
this.stop();
|
|
337
361
|
this.gainNode = audioContext.createGain();
|
|
338
|
-
|
|
362
|
+
|
|
363
|
+
// build the shared buffer if it was not made at load time, then play it
|
|
364
|
+
this.sound.buildSampleBuffer();
|
|
365
|
+
this.source = this.sound.sampleBuffer ?
|
|
366
|
+
playAudioBuffer(this.sound.sampleBuffer, this.volume, this.rate, this.pan, this.loop, this.gainNode, offset, this.onendedCallback) :
|
|
367
|
+
playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
339
368
|
if (this.source)
|
|
340
369
|
{
|
|
341
370
|
this.startTime = audioContext.currentTime - offset;
|
|
@@ -510,19 +539,54 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
510
539
|
|
|
511
540
|
if (!audioIsRunning())
|
|
512
541
|
{
|
|
513
|
-
// fix stalled audio,
|
|
542
|
+
// fix stalled audio, don't build a buffer that can't be played
|
|
514
543
|
audioContext.resume();
|
|
515
544
|
return;
|
|
516
545
|
}
|
|
517
546
|
|
|
518
|
-
|
|
547
|
+
const buffer = createAudioBuffer(sampleChannels, sampleRate);
|
|
548
|
+
return playAudioBuffer(buffer, volume, rate, pan, loop, gainNode, offset, onended);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Copy arrays of samples into a new audio buffer
|
|
552
|
+
* @param {Array} sampleChannels - Array of arrays of samples (for stereo playback)
|
|
553
|
+
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
554
|
+
* @return {AudioBuffer} - The audio buffer holding the samples
|
|
555
|
+
* @memberof Audio */
|
|
556
|
+
function createAudioBuffer(sampleChannels, sampleRate=audioDefaultSampleRate)
|
|
557
|
+
{
|
|
519
558
|
const channelCount = sampleChannels.length;
|
|
520
559
|
const sampleLength = sampleChannels[0].length;
|
|
521
560
|
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
522
|
-
const source = audioContext.createBufferSource();
|
|
523
|
-
|
|
524
|
-
// copy samples to buffer and setup source
|
|
525
561
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
562
|
+
return buffer;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** Play an audio buffer with given settings
|
|
566
|
+
* The buffer can be shared by any number of sounds playing at once
|
|
567
|
+
* @param {AudioBuffer} buffer - The audio buffer to play
|
|
568
|
+
* @param {number} [volume] - How much to scale volume by
|
|
569
|
+
* @param {number} [rate] - The playback rate to use
|
|
570
|
+
* @param {number} [pan] - How much to apply stereo panning
|
|
571
|
+
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
572
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
573
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
574
|
+
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
575
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
576
|
+
* @memberof Audio */
|
|
577
|
+
function playAudioBuffer(buffer, volume=1, rate=1, pan=0, loop=false, gainNode, offset=0, onended)
|
|
578
|
+
{
|
|
579
|
+
if (!soundEnable || headlessMode) return;
|
|
580
|
+
|
|
581
|
+
if (!audioIsRunning())
|
|
582
|
+
{
|
|
583
|
+
// fix stalled audio, this sound won't be able to play
|
|
584
|
+
audioContext.resume();
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// setup source, many sources can share one buffer
|
|
589
|
+
const source = audioContext.createBufferSource();
|
|
526
590
|
source.buffer = buffer;
|
|
527
591
|
source.playbackRate.value = rate;
|
|
528
592
|
source.loop = loop;
|