littlejsengine 1.18.28 → 1.19.3

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/plugins/zzfxm.js CHANGED
@@ -1,167 +1,160 @@
1
- /**
2
- * LittleJS ZzFXM Plugin
3
- * @namespace ZzFXM
4
- */
5
-
6
- 'use strict';
7
-
8
- /**
9
- * Music Object - Stores a zzfx music track for later use
10
- *
11
- * <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
12
- * @extends Sound
13
- * @memberof ZzFXM
14
- * @example
15
- * // create some music
16
- * const music_example = new Music(
17
- * [
18
- * [ // instruments
19
- * [,0,400] // simple note
20
- * ],
21
- * [ // patterns
22
- * [ // pattern 1
23
- * [ // channel 0
24
- * 0, -1, // instrument 0, left speaker
25
- * 1, 0, 9, 1 // channel notes
26
- * ],
27
- * [ // channel 1
28
- * 0, 1, // instrument 0, right speaker
29
- * 0, 12, 17, -1 // channel notes
30
- * ]
31
- * ],
32
- * ],
33
- * [0, 0, 0, 0], // sequence, play pattern 0 four times
34
- * 90 // BPM
35
- * ]);
36
- *
37
- * // play the music
38
- * music_example.play();
39
- */
40
- class ZzFXMusic extends Sound
41
- {
42
- /** Create a music object and cache the zzfx music samples for later use
43
- * @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
44
- */
45
- constructor(zzfxMusic)
46
- {
47
- super(undefined);
48
-
49
- if (!soundEnable || headlessMode) return;
50
- this.randomness = 0;
51
- this.sampleChannels = zzfxM(...zzfxMusic);
52
- this.sampleRate = audioDefaultSampleRate;
53
- }
54
-
55
- /** Play the music that loops by default
56
- * @param {number} [volume] - Volume to play the music at
57
- * @param {boolean} [loop] - Should the music loop?
58
- * @return {SoundInstance} - The sound instance
59
- */
60
- playMusic(volume=1, loop=true)
61
- { return super.play(undefined, volume, 1, 0, loop); }
62
- }
63
-
64
- ///////////////////////////////////////////////////////////////////////////////
65
- // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
66
-
67
- /** Generate samples for a ZzFM song with given parameters
68
- * @param {Array} instruments - Array of ZzFX sound parameters
69
- * @param {Array} patterns - Array of pattern data
70
- * @param {Array} sequence - Array of pattern indexes
71
- * @param {number} [BPM] - Playback speed of the song in BPM
72
- * @return {Array} - Left and right channel sample data
73
- * @memberof ZzFXM */
74
- function zzfxM(instruments, patterns, sequence, BPM = 125)
75
- {
76
- let i, j, k;
77
- let instrumentParameters;
78
- let note;
79
- let sample;
80
- let patternChannel;
81
- let notFirstBeat;
82
- let stop;
83
- let instrument;
84
- let attenuation;
85
- let outSampleOffset;
86
- let isSequenceEnd;
87
- let sampleOffset = 0;
88
- let nextSampleOffset;
89
- let sampleBuffer = [];
90
- let leftChannelBuffer = [];
91
- let rightChannelBuffer = [];
92
- let channelIndex = 0;
93
- let panning = 0;
94
- let hasMore = 1;
95
- let sampleCache = {};
96
- let beatLength = audioDefaultSampleRate / BPM * 60 >> 2;
97
-
98
- // for each channel in order until there are no more
99
- for (; hasMore; channelIndex++) {
100
-
101
- // reset current values
102
- sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
103
-
104
- // for each pattern in sequence
105
- sequence.forEach((patternIndex, sequenceIndex)=> {
106
- // get pattern for current channel, use empty 1 note pattern if none found
107
- patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
108
-
109
- // check if there are more channels
110
- hasMore |= patterns[patternIndex][channelIndex]&&1;
111
-
112
- // get next offset, use the length of first channel
113
- nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
114
- // for each beat in pattern, plus one extra if end of sequence
115
- isSequenceEnd = sequenceIndex === sequence.length - 1;
116
- for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
117
-
118
- // <channel-note>
119
- note = patternChannel[i];
120
-
121
- // stop if end, different instrument or new note
122
- stop = i === patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
123
- instrument !== (patternChannel[0] || 0) || note | 0;
124
-
125
- // fill buffer with samples for previous beat, most cpu intensive part
126
- for (j = 0; j < beatLength && notFirstBeat;
127
-
128
- // fade off attenuation at end of beat if stopping note, prevents clicking
129
- j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
130
- ) {
131
- // copy sample to stereo buffers with panning
132
- sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
133
- leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
134
- rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
135
- }
136
-
137
- // set up for next note
138
- if (note) {
139
- // set attenuation
140
- attenuation = note % 1;
141
- panning = patternChannel[1] || 0;
142
- if (note |= 0) {
143
- // get cached sample
144
- sampleBuffer = sampleCache[
145
- [
146
- instrument = patternChannel[sampleOffset = 0] || 0,
147
- note
148
- ]
149
- ] = sampleCache[[instrument, note]] || (
150
- // add sample to cache
151
- instrumentParameters = [...instruments[instrument]],
152
- instrumentParameters[2] = (instrumentParameters[2] || 220) * 2**(note / 12 - 1),
153
-
154
- // allow negative values to stop notes
155
- note > 0 ? zzfxG(...instrumentParameters) : []
156
- );
157
- }
158
- }
159
- }
160
-
161
- // update the sample offset
162
- outSampleOffset = nextSampleOffset;
163
- });
164
- }
165
-
166
- return [leftChannelBuffer, rightChannelBuffer];
1
+ /**
2
+ * LittleJS ZzFXM Plugin
3
+ * @namespace ZzFXM
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * Music Object - Stores a zzfx music track for later use
10
+ *
11
+ * <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
12
+ * @extends Sound
13
+ * @memberof ZzFXM
14
+ * @example
15
+ * // create some music
16
+ * const music_example = new ZzFXMusic(
17
+ * [
18
+ * [ // instruments
19
+ * [,0,400] // simple note
20
+ * ],
21
+ * [ // patterns
22
+ * [ // pattern 1
23
+ * [ // channel 0
24
+ * 0, -1, // instrument 0, left speaker
25
+ * 1, 0, 9, 1 // channel notes
26
+ * ],
27
+ * [ // channel 1
28
+ * 0, 1, // instrument 0, right speaker
29
+ * 0, 12, 17, -1 // channel notes
30
+ * ]
31
+ * ],
32
+ * ],
33
+ * [0, 0, 0, 0], // sequence, play pattern 0 four times
34
+ * 90 // BPM
35
+ * ]);
36
+ *
37
+ * // play the music on a loop
38
+ * music_example.playMusic();
39
+ */
40
+ class ZzFXMusic extends Sound
41
+ {
42
+ /** Create a music object and cache the zzfx music samples for later use
43
+ * @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
44
+ */
45
+ constructor(zzfxMusic)
46
+ {
47
+ super(undefined);
48
+
49
+ if (!soundEnable || headlessMode) return;
50
+ this.randomness = 0;
51
+ super.sampleChannels = zzfxM(...zzfxMusic); // the setter, without declaring a field that hides it in the typings
52
+ this.loadedPercent = 1; // generated in place, so it is loaded like a zzfx sound
53
+ this.onloadCallback?.(this);
54
+ }
55
+ }
56
+
57
+ ///////////////////////////////////////////////////////////////////////////////
58
+ // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
59
+
60
+ /** Generate samples for a ZzFM song with given parameters
61
+ * @param {Array} instruments - Array of ZzFX sound parameters
62
+ * @param {Array} patterns - Array of pattern data
63
+ * @param {Array} sequence - Array of pattern indexes
64
+ * @param {number} [BPM] - Playback speed of the song in BPM
65
+ * @return {Array} - Left and right channel sample data
66
+ * @memberof ZzFXM */
67
+ function zzfxM(instruments, patterns, sequence, BPM = 125)
68
+ {
69
+ let i, j, k;
70
+ let instrumentParameters;
71
+ let note;
72
+ let sample;
73
+ let patternChannel;
74
+ let notFirstBeat;
75
+ let stop;
76
+ let instrument;
77
+ let attenuation;
78
+ let outSampleOffset;
79
+ let isSequenceEnd;
80
+ let sampleOffset = 0;
81
+ let nextSampleOffset;
82
+ let sampleBuffer = [];
83
+ let leftChannelBuffer = [];
84
+ let rightChannelBuffer = [];
85
+ let channelIndex = 0;
86
+ let panning = 0;
87
+ let hasMore = 1;
88
+ let sampleCache = {};
89
+ let beatLength = audioDefaultSampleRate / BPM * 60 >> 2;
90
+
91
+ // for each channel in order until there are no more
92
+ for (; hasMore; channelIndex++) {
93
+
94
+ // reset current values
95
+ sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
96
+
97
+ // for each pattern in sequence
98
+ sequence.forEach((patternIndex, sequenceIndex)=> {
99
+ // get pattern for current channel, use empty 1 note pattern if none found
100
+ patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
101
+
102
+ // check if there are more channels
103
+ hasMore |= patterns[patternIndex][channelIndex]&&1;
104
+
105
+ // get next offset, use the length of first channel
106
+ nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
107
+ // for each beat in pattern, plus one extra if end of sequence
108
+ isSequenceEnd = sequenceIndex === sequence.length - 1;
109
+ for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
110
+
111
+ // <channel-note>
112
+ note = patternChannel[i];
113
+
114
+ // stop if end, different instrument or new note
115
+ stop = i === patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
116
+ instrument !== (patternChannel[0] || 0) || note | 0;
117
+
118
+ // fill buffer with samples for previous beat, most cpu intensive part
119
+ for (j = 0; j < beatLength && notFirstBeat;
120
+
121
+ // fade off attenuation at end of beat if stopping note, prevents clicking
122
+ j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
123
+ ) {
124
+ // copy sample to stereo buffers with panning
125
+ sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
126
+ leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
127
+ rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
128
+ }
129
+
130
+ // set up for next note
131
+ if (note) {
132
+ // set attenuation
133
+ attenuation = note % 1;
134
+ panning = patternChannel[1] || 0;
135
+ if (note |= 0) {
136
+ // get cached sample
137
+ sampleBuffer = sampleCache[
138
+ [
139
+ instrument = patternChannel[sampleOffset = 0] || 0,
140
+ note
141
+ ]
142
+ ] = sampleCache[[instrument, note]] || (
143
+ // add sample to cache
144
+ instrumentParameters = [...instruments[instrument]],
145
+ instrumentParameters[2] = (instrumentParameters[2] || 220) * 2**(note / 12 - 1),
146
+
147
+ // allow negative values to stop notes
148
+ note > 0 ? zzfxG(...instrumentParameters) : []
149
+ );
150
+ }
151
+ }
152
+ }
153
+
154
+ // update the sample offset
155
+ outSampleOffset = nextSampleOffset;
156
+ });
157
+ }
158
+
159
+ return [leftChannelBuffer, rightChannelBuffer];
167
160
  }