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.d.ts
CHANGED
|
@@ -2587,11 +2587,29 @@ declare module "littlejsengine" {
|
|
|
2587
2587
|
randomness: any;
|
|
2588
2588
|
/** @property {number} - Sample rate for this sound */
|
|
2589
2589
|
sampleRate: number;
|
|
2590
|
-
/** @property {number} -
|
|
2590
|
+
/** @property {number} - How many samples per channel this sound has */
|
|
2591
|
+
sampleLength: number;
|
|
2592
|
+
/** @property {AudioBuffer} - Decoded audio shared by every play of this sound
|
|
2593
|
+
* @type {AudioBuffer} */
|
|
2594
|
+
sampleBuffer: AudioBuffer;
|
|
2595
|
+
/** @private @type {Array<Array<number>|Float32Array>} */
|
|
2596
|
+
private _sampleChannels;
|
|
2597
|
+
/** @property {number} - Percentage of this sound currently loaded, sounds
|
|
2598
|
+
* fetched from a url stay at 0 until decoding completes */
|
|
2591
2599
|
loadedPercent: number;
|
|
2592
2600
|
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
2593
2601
|
onloadCallback: (sound: Sound) => Sound;
|
|
2594
|
-
sampleChannels
|
|
2602
|
+
/** @param {Array<Array<number>|Float32Array>} sampleChannels */
|
|
2603
|
+
set sampleChannels(arg: (number[] | Float32Array)[]);
|
|
2604
|
+
/** Sample data for each channel
|
|
2605
|
+
* Sounds keep their samples in an audio buffer, so reading this rebuilds
|
|
2606
|
+
* the arrays from it and caches them. The copies are safe to hold onto,
|
|
2607
|
+
* playing a sound detaches the buffer's own channel arrays.
|
|
2608
|
+
* @type {Array<Array<number>|Float32Array>} */
|
|
2609
|
+
get sampleChannels(): (number[] | Float32Array)[];
|
|
2610
|
+
/** Move this sound's samples into an audio buffer that every play can share
|
|
2611
|
+
* Does nothing if there is already a buffer or no samples to build one from */
|
|
2612
|
+
buildSampleBuffer(): void;
|
|
2595
2613
|
/** Play the sound
|
|
2596
2614
|
* Sounds may not play until a user interaction occurs
|
|
2597
2615
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
@@ -2744,6 +2762,25 @@ declare module "littlejsengine" {
|
|
|
2744
2762
|
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
2745
2763
|
* @memberof Audio */
|
|
2746
2764
|
export function playSamples(sampleChannels: any[], volume?: number, rate?: number, pan?: number, loop?: boolean, sampleRate?: number, gainNode?: GainNode, offset?: number, onended?: AudioEndedCallback): AudioBufferSourceNode;
|
|
2765
|
+
/** Play an audio buffer with given settings
|
|
2766
|
+
* The buffer can be shared by any number of sounds playing at once
|
|
2767
|
+
* @param {AudioBuffer} buffer - The audio buffer to play
|
|
2768
|
+
* @param {number} [volume] - How much to scale volume by
|
|
2769
|
+
* @param {number} [rate] - The playback rate to use
|
|
2770
|
+
* @param {number} [pan] - How much to apply stereo panning
|
|
2771
|
+
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
2772
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
2773
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
2774
|
+
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
2775
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
2776
|
+
* @memberof Audio */
|
|
2777
|
+
export function playAudioBuffer(buffer: AudioBuffer, volume?: number, rate?: number, pan?: number, loop?: boolean, gainNode?: GainNode, offset?: number, onended?: AudioEndedCallback): AudioBufferSourceNode;
|
|
2778
|
+
/** Copy arrays of samples into a new audio buffer
|
|
2779
|
+
* @param {Array} sampleChannels - Array of arrays of samples (for stereo playback)
|
|
2780
|
+
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
2781
|
+
* @return {AudioBuffer} - The audio buffer holding the samples
|
|
2782
|
+
* @memberof Audio */
|
|
2783
|
+
export function createAudioBuffer(sampleChannels: any[], sampleRate?: number): AudioBuffer;
|
|
2747
2784
|
/** Generate and play a ZzFX sound
|
|
2748
2785
|
*
|
|
2749
2786
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
package/dist/littlejs.esm.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}
|
|
@@ -6974,7 +6974,15 @@ class Sound
|
|
|
6974
6974
|
this.randomness = randomness ?? 0;
|
|
6975
6975
|
/** @property {number} - Sample rate for this sound */
|
|
6976
6976
|
this.sampleRate = audioDefaultSampleRate;
|
|
6977
|
-
/** @property {number} -
|
|
6977
|
+
/** @property {number} - How many samples per channel this sound has */
|
|
6978
|
+
this.sampleLength = 0;
|
|
6979
|
+
/** @property {AudioBuffer} - Decoded audio shared by every play of this sound
|
|
6980
|
+
* @type {AudioBuffer} */
|
|
6981
|
+
this.sampleBuffer = undefined;
|
|
6982
|
+
/** @private @type {Array<Array<number>|Float32Array>} */
|
|
6983
|
+
this._sampleChannels = undefined;
|
|
6984
|
+
/** @property {number} - Percentage of this sound currently loaded, sounds
|
|
6985
|
+
* fetched from a url stay at 0 until decoding completes */
|
|
6978
6986
|
this.loadedPercent = 0;
|
|
6979
6987
|
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
6980
6988
|
this.onloadCallback = onloadCallback;
|
|
@@ -6990,8 +6998,10 @@ class Sound
|
|
|
6990
6998
|
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
6991
6999
|
zzfxSound[randomnessIndex] = 0;
|
|
6992
7000
|
|
|
6993
|
-
// generate the zzfx samples
|
|
7001
|
+
// generate the zzfx samples, then hand them to an audio buffer so
|
|
7002
|
+
// the plain arrays can be released and every play shares the buffer
|
|
6994
7003
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
7004
|
+
this.buildSampleBuffer();
|
|
6995
7005
|
this.loadedPercent = 1;
|
|
6996
7006
|
onloadCallback?.(this);
|
|
6997
7007
|
}
|
|
@@ -7003,6 +7013,45 @@ class Sound
|
|
|
7003
7013
|
}
|
|
7004
7014
|
}
|
|
7005
7015
|
|
|
7016
|
+
/** Sample data for each channel
|
|
7017
|
+
* Sounds keep their samples in an audio buffer, so reading this rebuilds
|
|
7018
|
+
* the arrays from it and caches them. The copies are safe to hold onto,
|
|
7019
|
+
* playing a sound detaches the buffer's own channel arrays.
|
|
7020
|
+
* @type {Array<Array<number>|Float32Array>} */
|
|
7021
|
+
get sampleChannels()
|
|
7022
|
+
{
|
|
7023
|
+
const buffer = this.sampleBuffer;
|
|
7024
|
+
if (!this._sampleChannels && buffer)
|
|
7025
|
+
{
|
|
7026
|
+
const channels = [];
|
|
7027
|
+
for (let i = 0; i < buffer.numberOfChannels; i++)
|
|
7028
|
+
channels.push(buffer.getChannelData(i).slice());
|
|
7029
|
+
this._sampleChannels = channels;
|
|
7030
|
+
}
|
|
7031
|
+
return this._sampleChannels;
|
|
7032
|
+
}
|
|
7033
|
+
|
|
7034
|
+
/** @param {Array<Array<number>|Float32Array>} sampleChannels */
|
|
7035
|
+
set sampleChannels(sampleChannels)
|
|
7036
|
+
{
|
|
7037
|
+
// new samples invalidate the buffer built from the old ones
|
|
7038
|
+
this._sampleChannels = sampleChannels;
|
|
7039
|
+
this.sampleBuffer = undefined;
|
|
7040
|
+
this.sampleLength = sampleChannels?.[0]?.length || 0;
|
|
7041
|
+
}
|
|
7042
|
+
|
|
7043
|
+
/** Move this sound's samples into an audio buffer that every play can share
|
|
7044
|
+
* Does nothing if there is already a buffer or no samples to build one from */
|
|
7045
|
+
buildSampleBuffer()
|
|
7046
|
+
{
|
|
7047
|
+
if (this.sampleBuffer || !this._sampleChannels || headlessMode) return;
|
|
7048
|
+
|
|
7049
|
+
this.sampleBuffer = createAudioBuffer(this._sampleChannels, this.sampleRate);
|
|
7050
|
+
|
|
7051
|
+
// the buffer owns the samples now, release the arrays we built it from
|
|
7052
|
+
this._sampleChannels = undefined;
|
|
7053
|
+
}
|
|
7054
|
+
|
|
7006
7055
|
/** Play the sound
|
|
7007
7056
|
* Sounds may not play until a user interaction occurs
|
|
7008
7057
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
@@ -7021,7 +7070,7 @@ class Sound
|
|
|
7021
7070
|
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
7022
7071
|
|
|
7023
7072
|
if (!soundEnable || headlessMode) return;
|
|
7024
|
-
if (!this.
|
|
7073
|
+
if (!this.sampleBuffer && !this._sampleChannels) return;
|
|
7025
7074
|
|
|
7026
7075
|
let pan;
|
|
7027
7076
|
if (pos)
|
|
@@ -7088,7 +7137,7 @@ class Sound
|
|
|
7088
7137
|
* @return {number} - How long the sound is in seconds (0 if loading)
|
|
7089
7138
|
*/
|
|
7090
7139
|
getDuration()
|
|
7091
|
-
{ return this.
|
|
7140
|
+
{ return this.sampleLength / this.sampleRate || 0; }
|
|
7092
7141
|
|
|
7093
7142
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
7094
7143
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -7106,36 +7155,11 @@ class Sound
|
|
|
7106
7155
|
const arrayBuffer = await response.arrayBuffer();
|
|
7107
7156
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
7108
7157
|
|
|
7109
|
-
//
|
|
7110
|
-
|
|
7111
|
-
const samplesPerFrame = 1e5;
|
|
7112
|
-
const sampleChannels = [];
|
|
7113
|
-
for (let channel = 0; channel < channelCount; channel++)
|
|
7114
|
-
{
|
|
7115
|
-
const channelData = audioBuffer.getChannelData(channel);
|
|
7116
|
-
const channelLength = channelData.length;
|
|
7117
|
-
sampleChannels[channel] = new Array(channelLength);
|
|
7118
|
-
let sampleIndex = 0;
|
|
7119
|
-
while (sampleIndex < channelLength)
|
|
7120
|
-
{
|
|
7121
|
-
// yield to next frame
|
|
7122
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
7123
|
-
|
|
7124
|
-
// copy chunk of samples
|
|
7125
|
-
const endIndex = min(sampleIndex + samplesPerFrame, channelLength);
|
|
7126
|
-
for (; sampleIndex < endIndex; sampleIndex++)
|
|
7127
|
-
sampleChannels[channel][sampleIndex] = channelData[sampleIndex];
|
|
7128
|
-
|
|
7129
|
-
// update loaded percent
|
|
7130
|
-
const samplesTotal = channelCount * channelLength;
|
|
7131
|
-
const samplesProcessed = channel * channelLength + sampleIndex;
|
|
7132
|
-
this.loadedPercent = samplesProcessed / samplesTotal;
|
|
7133
|
-
}
|
|
7134
|
-
}
|
|
7135
|
-
|
|
7136
|
-
// setup the sound to be played
|
|
7158
|
+
// keep the decoded buffer as is, it is exactly what playback needs and
|
|
7159
|
+
// every play shares it, no channel data is read or copied
|
|
7137
7160
|
this.sampleRate = audioBuffer.sampleRate;
|
|
7138
|
-
this.
|
|
7161
|
+
this.sampleLength = audioBuffer.length;
|
|
7162
|
+
this.sampleBuffer = audioBuffer;
|
|
7139
7163
|
this.loadedPercent = 1;
|
|
7140
7164
|
this.onloadCallback?.(this);
|
|
7141
7165
|
}
|
|
@@ -7211,7 +7235,12 @@ class SoundInstance
|
|
|
7211
7235
|
if (this.isPlaying())
|
|
7212
7236
|
this.stop();
|
|
7213
7237
|
this.gainNode = audioContext.createGain();
|
|
7214
|
-
|
|
7238
|
+
|
|
7239
|
+
// build the shared buffer if it was not made at load time, then play it
|
|
7240
|
+
this.sound.buildSampleBuffer();
|
|
7241
|
+
this.source = this.sound.sampleBuffer ?
|
|
7242
|
+
playAudioBuffer(this.sound.sampleBuffer, this.volume, this.rate, this.pan, this.loop, this.gainNode, offset, this.onendedCallback) :
|
|
7243
|
+
playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
7215
7244
|
if (this.source)
|
|
7216
7245
|
{
|
|
7217
7246
|
this.startTime = audioContext.currentTime - offset;
|
|
@@ -7386,19 +7415,54 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
7386
7415
|
|
|
7387
7416
|
if (!audioIsRunning())
|
|
7388
7417
|
{
|
|
7389
|
-
// fix stalled audio,
|
|
7418
|
+
// fix stalled audio, don't build a buffer that can't be played
|
|
7390
7419
|
audioContext.resume();
|
|
7391
7420
|
return;
|
|
7392
7421
|
}
|
|
7393
7422
|
|
|
7394
|
-
|
|
7423
|
+
const buffer = createAudioBuffer(sampleChannels, sampleRate);
|
|
7424
|
+
return playAudioBuffer(buffer, volume, rate, pan, loop, gainNode, offset, onended);
|
|
7425
|
+
}
|
|
7426
|
+
|
|
7427
|
+
/** Copy arrays of samples into a new audio buffer
|
|
7428
|
+
* @param {Array} sampleChannels - Array of arrays of samples (for stereo playback)
|
|
7429
|
+
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
7430
|
+
* @return {AudioBuffer} - The audio buffer holding the samples
|
|
7431
|
+
* @memberof Audio */
|
|
7432
|
+
function createAudioBuffer(sampleChannels, sampleRate=audioDefaultSampleRate)
|
|
7433
|
+
{
|
|
7395
7434
|
const channelCount = sampleChannels.length;
|
|
7396
7435
|
const sampleLength = sampleChannels[0].length;
|
|
7397
7436
|
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
7398
|
-
const source = audioContext.createBufferSource();
|
|
7399
|
-
|
|
7400
|
-
// copy samples to buffer and setup source
|
|
7401
7437
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
7438
|
+
return buffer;
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7441
|
+
/** Play an audio buffer with given settings
|
|
7442
|
+
* The buffer can be shared by any number of sounds playing at once
|
|
7443
|
+
* @param {AudioBuffer} buffer - The audio buffer to play
|
|
7444
|
+
* @param {number} [volume] - How much to scale volume by
|
|
7445
|
+
* @param {number} [rate] - The playback rate to use
|
|
7446
|
+
* @param {number} [pan] - How much to apply stereo panning
|
|
7447
|
+
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
7448
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
7449
|
+
* @param {number} [offset] - Offset in seconds to start playback from
|
|
7450
|
+
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
7451
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
7452
|
+
* @memberof Audio */
|
|
7453
|
+
function playAudioBuffer(buffer, volume=1, rate=1, pan=0, loop=false, gainNode, offset=0, onended)
|
|
7454
|
+
{
|
|
7455
|
+
if (!soundEnable || headlessMode) return;
|
|
7456
|
+
|
|
7457
|
+
if (!audioIsRunning())
|
|
7458
|
+
{
|
|
7459
|
+
// fix stalled audio, this sound won't be able to play
|
|
7460
|
+
audioContext.resume();
|
|
7461
|
+
return;
|
|
7462
|
+
}
|
|
7463
|
+
|
|
7464
|
+
// setup source, many sources can share one buffer
|
|
7465
|
+
const source = audioContext.createBufferSource();
|
|
7402
7466
|
source.buffer = buffer;
|
|
7403
7467
|
source.playbackRate.value = rate;
|
|
7404
7468
|
source.loop = loop;
|
|
@@ -17339,6 +17403,8 @@ export
|
|
|
17339
17403
|
speakStop,
|
|
17340
17404
|
getNoteFrequency,
|
|
17341
17405
|
playSamples,
|
|
17406
|
+
playAudioBuffer,
|
|
17407
|
+
createAudioBuffer,
|
|
17342
17408
|
zzfx,
|
|
17343
17409
|
zzfxG,
|
|
17344
17410
|
|