spessasynth_lib 0.0.1

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.
Files changed (154) hide show
  1. package/.idea/modules.xml +8 -0
  2. package/.idea/spessasynth_lib.iml +12 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/copy_version.sh +38 -0
  5. package/index.js +73 -0
  6. package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  7. package/package/@types/index.d.ts +34 -0
  8. package/package/@types/midi_handler/midi_handler.d.ts +39 -0
  9. package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
  10. package/package/@types/midi_parser/midi_data.d.ts +95 -0
  11. package/package/@types/midi_parser/midi_editor.d.ts +45 -0
  12. package/package/@types/midi_parser/midi_loader.d.ts +100 -0
  13. package/package/@types/midi_parser/midi_message.d.ts +154 -0
  14. package/package/@types/midi_parser/midi_writer.d.ts +6 -0
  15. package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  16. package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  17. package/package/@types/sequencer/sequencer.d.ts +180 -0
  18. package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  19. package/package/@types/soundfont/read/generators.d.ts +98 -0
  20. package/package/@types/soundfont/read/instruments.d.ts +50 -0
  21. package/package/@types/soundfont/read/modulators.d.ts +73 -0
  22. package/package/@types/soundfont/read/presets.d.ts +87 -0
  23. package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  24. package/package/@types/soundfont/read/samples.d.ts +134 -0
  25. package/package/@types/soundfont/read/zones.d.ts +141 -0
  26. package/package/@types/soundfont/soundfont.d.ts +76 -0
  27. package/package/@types/soundfont/write/ibag.d.ts +6 -0
  28. package/package/@types/soundfont/write/igen.d.ts +6 -0
  29. package/package/@types/soundfont/write/imod.d.ts +6 -0
  30. package/package/@types/soundfont/write/inst.d.ts +6 -0
  31. package/package/@types/soundfont/write/pbag.d.ts +6 -0
  32. package/package/@types/soundfont/write/pgen.d.ts +6 -0
  33. package/package/@types/soundfont/write/phdr.d.ts +6 -0
  34. package/package/@types/soundfont/write/pmod.d.ts +6 -0
  35. package/package/@types/soundfont/write/sdta.d.ts +11 -0
  36. package/package/@types/soundfont/write/shdr.d.ts +8 -0
  37. package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  38. package/package/@types/soundfont/write/write.d.ts +21 -0
  39. package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  40. package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  41. package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  42. package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  43. package/package/@types/synthetizer/synthetizer.d.ts +294 -0
  44. package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
  45. package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  46. package/package/@types/synthetizer/worklet_url.d.ts +5 -0
  47. package/package/@types/utils/buffer_to_wav.d.ts +8 -0
  48. package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  49. package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  50. package/package/@types/utils/byte_functions/string.d.ts +22 -0
  51. package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  52. package/package/@types/utils/indexed_array.d.ts +21 -0
  53. package/package/@types/utils/loggin.d.ts +26 -0
  54. package/package/@types/utils/other.d.ts +32 -0
  55. package/package/LICENSE +26 -0
  56. package/package/README.md +84 -0
  57. package/package/externals/NOTICE +9 -0
  58. package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  59. package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  60. package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  61. package/package/externals/stbvorbis_sync/LICENSE +202 -0
  62. package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  63. package/package/index.js +73 -0
  64. package/package/midi_handler/README.md +3 -0
  65. package/package/midi_handler/midi_handler.js +118 -0
  66. package/package/midi_handler/web_midi_link.js +41 -0
  67. package/package/midi_parser/README.md +3 -0
  68. package/package/midi_parser/midi_data.js +121 -0
  69. package/package/midi_parser/midi_editor.js +557 -0
  70. package/package/midi_parser/midi_loader.js +502 -0
  71. package/package/midi_parser/midi_message.js +234 -0
  72. package/package/midi_parser/midi_writer.js +95 -0
  73. package/package/midi_parser/rmidi_writer.js +271 -0
  74. package/package/midi_parser/used_keys_loaded.js +172 -0
  75. package/package/package.json +43 -0
  76. package/package/sequencer/README.md +23 -0
  77. package/package/sequencer/sequencer.js +439 -0
  78. package/package/sequencer/worklet_sequencer/events.js +92 -0
  79. package/package/sequencer/worklet_sequencer/play.js +309 -0
  80. package/package/sequencer/worklet_sequencer/process_event.js +167 -0
  81. package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
  82. package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  83. package/package/sequencer/worklet_sequencer/song_control.js +193 -0
  84. package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  85. package/package/soundfont/README.md +8 -0
  86. package/package/soundfont/read/generators.js +212 -0
  87. package/package/soundfont/read/instruments.js +125 -0
  88. package/package/soundfont/read/modulators.js +249 -0
  89. package/package/soundfont/read/presets.js +300 -0
  90. package/package/soundfont/read/riff_chunk.js +81 -0
  91. package/package/soundfont/read/samples.js +398 -0
  92. package/package/soundfont/read/zones.js +310 -0
  93. package/package/soundfont/soundfont.js +357 -0
  94. package/package/soundfont/write/ibag.js +39 -0
  95. package/package/soundfont/write/igen.js +75 -0
  96. package/package/soundfont/write/imod.js +46 -0
  97. package/package/soundfont/write/inst.js +34 -0
  98. package/package/soundfont/write/pbag.js +39 -0
  99. package/package/soundfont/write/pgen.js +77 -0
  100. package/package/soundfont/write/phdr.js +42 -0
  101. package/package/soundfont/write/pmod.js +46 -0
  102. package/package/soundfont/write/sdta.js +72 -0
  103. package/package/soundfont/write/shdr.js +54 -0
  104. package/package/soundfont/write/soundfont_trimmer.js +169 -0
  105. package/package/soundfont/write/write.js +180 -0
  106. package/package/synthetizer/README.md +6 -0
  107. package/package/synthetizer/audio_effects/effects_config.js +21 -0
  108. package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  109. package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  110. package/package/synthetizer/audio_effects/reverb.js +24 -0
  111. package/package/synthetizer/synth_event_handler.js +156 -0
  112. package/package/synthetizer/synthetizer.js +766 -0
  113. package/package/synthetizer/worklet_processor.min.js +13 -0
  114. package/package/synthetizer/worklet_system/README.md +6 -0
  115. package/package/synthetizer/worklet_system/main_processor.js +363 -0
  116. package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
  117. package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  118. package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
  119. package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
  120. package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  121. package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  122. package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  123. package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  124. package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  125. package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  126. package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  127. package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  128. package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  129. package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  130. package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  131. package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
  132. package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  133. package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  134. package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  135. package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  136. package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  137. package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  138. package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  139. package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  140. package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  141. package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  142. package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  143. package/package/synthetizer/worklet_url.js +5 -0
  144. package/package/utils/README.md +4 -0
  145. package/package/utils/buffer_to_wav.js +101 -0
  146. package/package/utils/byte_functions/big_endian.js +28 -0
  147. package/package/utils/byte_functions/little_endian.js +74 -0
  148. package/package/utils/byte_functions/string.js +97 -0
  149. package/package/utils/byte_functions/variable_length_quantity.js +37 -0
  150. package/package/utils/encode_vorbis.js +30 -0
  151. package/package/utils/indexed_array.js +41 -0
  152. package/package/utils/loggin.js +79 -0
  153. package/package/utils/other.js +54 -0
  154. package/package.json +43 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * unit_converter.js
3
+ * purpose: converts soundfont units into more useable values with the use of lookup tables to improve performance
4
+ */
5
+
6
+
7
+ // timecent lookup table
8
+ const MIN_TIMECENT = -15000;
9
+ const MAX_TIMECENT = 15000;
10
+ const timecentLookupTable = new Float32Array(MAX_TIMECENT - MIN_TIMECENT + 1);
11
+ for (let i = 0; i < timecentLookupTable.length; i++) {
12
+ const timecents = MIN_TIMECENT + i;
13
+ timecentLookupTable[i] = Math.pow(2, timecents / 1200);
14
+ }
15
+
16
+ /**
17
+ * Converts timecents to seconds
18
+ * @param timecents {number} timecents
19
+ * @returns {number} seconds
20
+ */
21
+ export function timecentsToSeconds(timecents)
22
+ {
23
+ return timecentLookupTable[timecents - MIN_TIMECENT];
24
+ }
25
+
26
+ // abs cent lookup table
27
+ const MIN_ABS_CENT = -20000; // freqVibLfo
28
+ const MAX_ABS_CENT = 16500; // filterFc
29
+ const absoluteCentLookupTable = new Float32Array(MAX_ABS_CENT - MIN_ABS_CENT + 1);
30
+ for (let i = 0; i < absoluteCentLookupTable.length; i++) {
31
+ const absoluteCents = MIN_ABS_CENT + i;
32
+ absoluteCentLookupTable[i] = 440 * Math.pow(2, (absoluteCents - 6900) / 1200);
33
+ }
34
+
35
+ /**
36
+ * Converts absolute cents to hertz
37
+ * @param cents {number} absolute cents
38
+ * @returns {number} hertz
39
+ */
40
+ export function absCentsToHz(cents)
41
+ {
42
+ if(cents < MIN_ABS_CENT || cents > MAX_ABS_CENT)
43
+ {
44
+ return 440 * Math.pow(2, (cents - 6900) / 1200);
45
+ }
46
+ return absoluteCentLookupTable[~~(cents) - MIN_ABS_CENT];
47
+ }
48
+
49
+ // decibel lookup table (2 points of precision)
50
+ const MIN_DECIBELS = -1660;
51
+ const MAX_DECIBELS = 1600;
52
+ const decibelLookUpTable = new Float32Array((MAX_DECIBELS - MIN_DECIBELS) * 100 + 1);
53
+ for (let i = 0; i < decibelLookUpTable.length; i++) {
54
+ const decibels = (MIN_DECIBELS * 100 + i) / 100;
55
+ decibelLookUpTable[i] = Math.pow(10, -decibels / 20);
56
+ }
57
+
58
+ /**
59
+ * convers decibel attenuation to gain
60
+ * @param decibels {number} the decibel attenuation
61
+ * @returns {number} gain
62
+ */
63
+ export function decibelAttenuationToGain(decibels)
64
+ {
65
+ return decibelLookUpTable[Math.floor((decibels - MIN_DECIBELS) * 100)];
66
+ }
@@ -0,0 +1,265 @@
1
+ import { decibelAttenuationToGain, timecentsToSeconds } from './unit_converter.js'
2
+ import { generatorTypes } from '../../../soundfont/read/generators.js'
3
+
4
+ /**
5
+ * volume_envelope.js
6
+ * purpose: applies a volume envelope for a given voice
7
+ */
8
+
9
+ /**
10
+ * @typedef {Object} WorkletVolumeEnvelope
11
+ * @property {number} currentAttenuationDb - current voice attenuation in dB (current sample)
12
+ * @property {0|1|2|3|4} state - state of the volume envelope. 0 is delay, 1 is attack, 2 is hold, 3 is decay, 4 is sustain
13
+ * @property {number} releaseStartDb - the dB attenuation of the voice when it was released
14
+ * @property {number} currentReleaseGain - the current linear gain of the release phase
15
+ *
16
+ * @property {number} attackDuration - the duration of the attack phase, in seconds
17
+ * @property {number} decayDuration - the duration of the decay phase, in seconds
18
+ *
19
+ * @property {number} attenuation - the absolute attenuation in dB
20
+ * @property {number} releaseDuration - the duration of the release phase in seconds
21
+ * @property {number} sustainDb - the sustain amount in dB
22
+ *
23
+ * @property {number} delayEnd - the time when delay ends, in absolute seconds
24
+ * @property {number} attackEnd - the time when the attack phase ends, in absolute seconds
25
+ * @property {number} holdEnd - the time when the hold phase ends, in absolute seconds
26
+ * @property {number} decayEnd - the time when the decay phase ends, in absolute seconds
27
+ */
28
+
29
+ /**
30
+ * @type {WorkletVolumeEnvelope}
31
+ */
32
+ export const DEFAULT_WORKLET_VOLUME_ENVELOPE = {
33
+ attenuation: 100,
34
+ currentAttenuationDb: 100,
35
+ state: 0,
36
+ releaseStartDb: 100,
37
+ attackDuration: 0,
38
+ decayDuration: 0,
39
+ releaseDuration: 0,
40
+ sustainDb: 0,
41
+ delayEnd: 0,
42
+ attackEnd: 0,
43
+ holdEnd: 0,
44
+ decayEnd: 0,
45
+ currentReleaseGain: 1,
46
+ }
47
+
48
+ export const VOLUME_ENVELOPE_SMOOTHING_FACTOR = 0.001;
49
+
50
+ const DB_SILENCE = 100;
51
+ const GAIN_SILENCE = 0.005;
52
+
53
+ /**
54
+ * VOL ENV STATES:
55
+ * 0 - delay
56
+ * 1 - attack
57
+ * 2 - hold/peak
58
+ * 3 - decay
59
+ * 4 - sustain
60
+ * release is indicated by isInRelease property
61
+ */
62
+
63
+ /**
64
+ * Recalculates the times of the volume envelope
65
+ * @param voice {WorkletVoice} the voice we're working on
66
+ */
67
+ export function recalculateVolumeEnvelope(voice)
68
+ {
69
+ const env = voice.volumeEnvelope;
70
+ // calculate durations
71
+ env.attackDuration = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
72
+ env.decayDuration = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayVolEnv]
73
+ + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay]));
74
+ env.releaseDuration = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
75
+
76
+ // calculate absolute times (they can change so we have to recalculate every time
77
+ env.attenuation = voice.modulatedGenerators[generatorTypes.initialAttenuation] / 10; // divide by ten to get decibelts
78
+ env.sustainDb = voice.volumeEnvelope.attenuation + voice.modulatedGenerators[generatorTypes.sustainVolEnv] / 10;
79
+
80
+ // calculate absolute end time
81
+ env.delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVolEnv]) + voice.startTime;
82
+ env.attackEnd = env.attackDuration + env.delayEnd;
83
+
84
+ // make sure to take keyNumToVolEnvHold into account!!!
85
+ env.holdEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdVolEnv]
86
+ + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvHold]))
87
+ + env.attackEnd;
88
+
89
+ env.decayEnd = env.decayDuration + env.holdEnd;
90
+ // check if voice is in release
91
+ if(voice.isInRelease)
92
+ {
93
+ // calculate the db attenuation at the time of release (not a constant because it can change (ex, volume set to 0, the sound should cut off)
94
+ switch (env.state) {
95
+ case 0:
96
+ env.releaseStartDb = 0;
97
+ break;
98
+
99
+ case 1:
100
+ // attack phase
101
+ // attack is linear (in gain) so we need to do get db from that
102
+ let elapsed = 1 - ((env.attackEnd - voice.releaseStartTime) / env.attackDuration);
103
+ // calculate the gain that the attack would have
104
+ let attackGain = elapsed * decibelAttenuationToGain(env.attenuation);
105
+
106
+ // turn that into db
107
+ env.releaseStartDb = 20 * Math.log10(attackGain) * -1;
108
+ break;
109
+
110
+ case 2:
111
+ env.releaseStartDb = env.attenuation;
112
+ break;
113
+
114
+ case 3:
115
+ env.releaseStartDb = (1 - (env.decayEnd - voice.releaseStartTime) / env.decayDuration) * (env.sustainDb - env.attenuation) + env.attenuation;
116
+ break;
117
+
118
+ case 4:
119
+ env.releaseStartDb = env.sustainDb;
120
+ break;
121
+
122
+ default:
123
+ env.releaseStartDb = env.currentAttenuationDb;
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Applies volume envelope gain to the given output buffer
130
+ * @param voice {WorkletVoice} the voice we're working on
131
+ * @param audioBuffer {Float32Array} the audio buffer to modify
132
+ * @param currentTime {number} the current audio time
133
+ * @param centibelOffset {number} the centibel offset of volume, for modLFOtoVolume
134
+ * @param sampleTime {number} single sample time in seconds, usually 1 / 44100 of a second
135
+ * @param smoothingFactor {number} the adjusted smoothing factor for the envelope
136
+ */
137
+
138
+ export function applyVolumeEnvelope(voice, audioBuffer, currentTime, centibelOffset, sampleTime, smoothingFactor)
139
+ {
140
+ let decibelOffset = centibelOffset / 10;
141
+ const env = voice.volumeEnvelope;
142
+
143
+ // RELEASE PHASE
144
+ if(voice.isInRelease)
145
+ {
146
+ // release needs a more aggressive smoothing factor as the instant notes don't end instantly when they should
147
+ const releaseSmoothingFactor = smoothingFactor * 10;
148
+ const releaseStartDb = env.releaseStartDb + decibelOffset;
149
+ let elapsedRelease = currentTime - voice.releaseStartTime;
150
+ let dbDifference = DB_SILENCE - releaseStartDb;
151
+ let gain = env.currentReleaseGain;
152
+ for (let i = 0; i < audioBuffer.length; i++)
153
+ {
154
+ let db = (elapsedRelease / env.releaseDuration) * dbDifference + releaseStartDb;
155
+ gain = decibelAttenuationToGain(db + decibelOffset);
156
+ env.currentReleaseGain += (gain - env.currentReleaseGain) * releaseSmoothingFactor;
157
+ audioBuffer[i] *= env.currentReleaseGain;
158
+ elapsedRelease += sampleTime;
159
+ }
160
+
161
+ if(env.currentReleaseGain <= GAIN_SILENCE)
162
+ {
163
+ voice.finished = true;
164
+ }
165
+ return;
166
+ }
167
+
168
+ let currentFrameTime = currentTime;
169
+ let filledBuffer = 0;
170
+ switch(env.state)
171
+ {
172
+ case 0:
173
+ // delay phase, no sound is produced
174
+ while(currentFrameTime < env.delayEnd)
175
+ {
176
+ env.currentAttenuationDb = DB_SILENCE;
177
+ audioBuffer[filledBuffer] = 0;
178
+
179
+ currentFrameTime += sampleTime;
180
+ if(++filledBuffer >= audioBuffer.length)
181
+ {
182
+ return;
183
+ }
184
+ }
185
+ env.state++;
186
+ // fallthrough
187
+
188
+ case 1:
189
+ // attack phase: ramp from 0 to attenuation
190
+ while(currentFrameTime < env.attackEnd)
191
+ {
192
+ // Special case: linear gain ramp instead of linear db ramp
193
+ let linearAttenuation = 1 - (env.attackEnd - currentFrameTime) / env.attackDuration; // 0 to 1
194
+ const gain = linearAttenuation * decibelAttenuationToGain(env.attenuation + decibelOffset)
195
+ audioBuffer[filledBuffer] *= gain;
196
+
197
+ // set current attenuation to peak as its invalid during this phase
198
+ env.currentAttenuationDb = env.attenuation;
199
+
200
+ currentFrameTime += sampleTime;
201
+ if(++filledBuffer >= audioBuffer.length)
202
+ {
203
+ return;
204
+ }
205
+ }
206
+ env.state++;
207
+ // fallthrough
208
+
209
+ case 2:
210
+ // hold/peak phase: stay at attenuation
211
+ while(currentFrameTime < env.holdEnd)
212
+ {
213
+ const newAttenuation = env.attenuation
214
+ + decibelOffset;
215
+
216
+ // interpolate attenuation to prevent clicking
217
+ env.currentAttenuationDb += (newAttenuation - env.currentAttenuationDb) * smoothingFactor;
218
+ audioBuffer[filledBuffer] *= decibelAttenuationToGain(env.currentAttenuationDb);
219
+
220
+ currentFrameTime += sampleTime;
221
+ if(++filledBuffer >= audioBuffer.length)
222
+ {
223
+ return;
224
+ }
225
+ }
226
+ env.state++;
227
+ // fallthrough
228
+
229
+ case 3:
230
+ // decay phase: linear ramp from attenuation to sustain
231
+ while(currentFrameTime < env.decayEnd)
232
+ {
233
+ const newAttenuation = (1 - (env.decayEnd - currentFrameTime) / env.decayDuration) * (env.sustainDb - env.attenuation) + env.attenuation
234
+ + decibelOffset;
235
+
236
+ // interpolate attenuation to prevent clicking
237
+ env.currentAttenuationDb += (newAttenuation - env.currentAttenuationDb) * smoothingFactor;
238
+ audioBuffer[filledBuffer] *= decibelAttenuationToGain(env.currentAttenuationDb);
239
+
240
+ currentFrameTime += sampleTime;
241
+ if(++filledBuffer >= audioBuffer.length)
242
+ {
243
+ return;
244
+ }
245
+ }
246
+ env.state++;
247
+ // fallthrough
248
+
249
+ case 4:
250
+ // sustain phase: stay at sustain
251
+ while(true)
252
+ {
253
+ // interpolate attenuation to prevent clicking
254
+ const newAttenuation = env.sustainDb
255
+ + decibelOffset;
256
+ env.currentAttenuationDb += (newAttenuation - env.currentAttenuationDb) * smoothingFactor;
257
+ audioBuffer[filledBuffer] *= decibelAttenuationToGain(env.currentAttenuationDb);
258
+ if(++filledBuffer >= audioBuffer.length)
259
+ {
260
+ return;
261
+ }
262
+ }
263
+
264
+ }
265
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * wavetable_oscillator.js
3
+ * purpose: plays back raw audio data at an arbitrary playback rate
4
+ */
5
+
6
+
7
+ /**
8
+ * Fills the output buffer with raw sample data
9
+ * @param voice {WorkletVoice} the voice we're working on
10
+ * @param sampleData {Float32Array} the sample data to write with
11
+ * @param outputBuffer {Float32Array} the output buffer to write to
12
+ */
13
+ export function getOscillatorData(voice, sampleData, outputBuffer)
14
+ {
15
+ let cur = voice.sample.cursor;
16
+ const loop = (voice.sample.loopingMode === 1) || (voice.sample.loopingMode === 3 && !voice.isInRelease);
17
+ const loopLength = voice.sample.loopEnd - voice.sample.loopStart;
18
+
19
+ if(loop)
20
+ {
21
+ for (let i = 0; i < outputBuffer.length; i++) {
22
+ // check for loop
23
+ while(cur >= voice.sample.loopEnd) {
24
+ cur -= loopLength;
25
+ }
26
+
27
+ // grab the 2 nearest points
28
+ const floor = ~~cur;
29
+ let ceil = floor + 1;
30
+
31
+ while(ceil >= voice.sample.loopEnd) {
32
+ ceil -= loopLength;
33
+ }
34
+
35
+ const fraction = cur - floor;
36
+
37
+ // grab the samples and interpolate
38
+ const upper = sampleData[ceil];
39
+ const lower = sampleData[floor];
40
+ outputBuffer[i] = (lower + (upper - lower) * fraction);
41
+
42
+ // commented code because it's probably gonna come handy... (it did like 6 times already :/)
43
+ // if(isNaN(outputBuffer[i]))
44
+ // {
45
+ // console.error(voice, upper, lower, floor, ceil, cur)
46
+ // throw "NAN ALERT";
47
+ // }
48
+
49
+ cur += voice.sample.playbackStep * voice.currentTuningCalculated;
50
+ }
51
+ }
52
+ else
53
+ {
54
+ // check and correct end errors
55
+ if(voice.sample.end >= sampleData.length)
56
+ {
57
+ voice.sample.end = sampleData.length - 1;
58
+ }
59
+ for (let i = 0; i < outputBuffer.length; i++) {
60
+
61
+ // linear interpolation
62
+ const floor = ~~cur;
63
+ const ceil = floor + 1;
64
+
65
+ // flag the voice as finished if needed
66
+ if(ceil >= voice.sample.end)
67
+ {
68
+ voice.finished = true;
69
+ return;
70
+ }
71
+
72
+ const fraction = cur - floor;
73
+
74
+ // grab the samples and interpolate
75
+ const upper = sampleData[ceil];
76
+ const lower = sampleData[floor];
77
+ outputBuffer[i] = (lower + (upper - lower) * fraction);
78
+
79
+ cur += voice.sample.playbackStep * voice.currentTuningCalculated;
80
+ }
81
+ }
82
+ voice.sample.cursor = cur;
83
+ }
@@ -0,0 +1,234 @@
1
+ import { modulatorSources } from '../../../soundfont/read/modulators.js'
2
+ import { getModulatorCurveValue, MOD_PRECOMPUTED_LENGTH } from './modulator_curves.js'
3
+ import { NON_CC_INDEX_OFFSET } from './worklet_processor_channel.js'
4
+ import { recalculateVolumeEnvelope } from './volume_envelope.js'
5
+ import { generatorTypes } from '../../../soundfont/read/generators.js'
6
+
7
+ /**
8
+ * worklet_modulator.js
9
+ * purpose: precomputes all curve types and computes modulators
10
+ */
11
+
12
+ /**
13
+ * Computes a given modulator
14
+ * @param controllerTable {Int16Array} all midi controllers as 14bit values + the non controller indexes, starting at 128
15
+ * @param modulator {Modulator} the modulator to compute
16
+ * @param voice {WorkletVoice} the voice belonging to the modulator
17
+ * @returns {number} the computed value
18
+ */
19
+ export function computeWorkletModulator(controllerTable, modulator, voice)
20
+ {
21
+ if(modulator.transformAmount === 0)
22
+ {
23
+ return 0;
24
+ }
25
+ // mapped to 0-16384
26
+ let rawSourceValue;
27
+ if(modulator.sourceUsesCC)
28
+ {
29
+ rawSourceValue = controllerTable[modulator.sourceIndex];
30
+ }
31
+ else
32
+ {
33
+ const index = modulator.sourceIndex + NON_CC_INDEX_OFFSET;
34
+ switch (modulator.sourceIndex)
35
+ {
36
+ case modulatorSources.noController:
37
+ rawSourceValue = 16383; // equals to 1
38
+ break;
39
+
40
+ case modulatorSources.noteOnKeyNum:
41
+ rawSourceValue = voice.midiNote << 7;
42
+ break;
43
+
44
+ case modulatorSources.noteOnVelocity:
45
+ rawSourceValue = voice.velocity << 7;
46
+ break;
47
+
48
+ case modulatorSources.polyPressure:
49
+ rawSourceValue = voice.pressure << 7;
50
+ break;
51
+
52
+ default:
53
+ rawSourceValue = controllerTable[index]; // pitch bend and range are stored in the cc table
54
+ break;
55
+ }
56
+
57
+ }
58
+
59
+ const sourceValue = transforms[modulator.sourceCurveType][modulator.sourcePolarity][modulator.sourceDirection][rawSourceValue];
60
+
61
+ // mapped to 0-127
62
+ let rawSecondSrcValue;
63
+ if(modulator.secSrcUsesCC)
64
+ {
65
+ rawSecondSrcValue = controllerTable[modulator.secSrcIndex];
66
+ }
67
+ else
68
+ {
69
+ const index = modulator.secSrcIndex + NON_CC_INDEX_OFFSET;
70
+ switch (modulator.secSrcIndex)
71
+ {
72
+ case modulatorSources.noController:
73
+ rawSecondSrcValue = 16383; // equals to 1
74
+ break;
75
+
76
+ case modulatorSources.noteOnKeyNum:
77
+ rawSecondSrcValue = voice.midiNote << 7;
78
+ break;
79
+
80
+ case modulatorSources.noteOnVelocity:
81
+ rawSecondSrcValue = voice.velocity << 7;
82
+ break;
83
+
84
+ case modulatorSources.polyPressure:
85
+ rawSecondSrcValue = voice.pressure << 7;
86
+ break;
87
+
88
+ default:
89
+ rawSecondSrcValue = controllerTable[index]; // pitch bend and range are stored in the cc table
90
+ }
91
+
92
+ }
93
+ const secondSrcValue = transforms[modulator.secSrcCurveType][modulator.secSrcPolarity][modulator.secSrcDirection][rawSecondSrcValue];
94
+
95
+
96
+ // compute the modulator
97
+ const computedValue = sourceValue * secondSrcValue * modulator.transformAmount;
98
+
99
+ if(modulator.transformType === 2)
100
+ {
101
+ // abs value
102
+ return Math.abs(computedValue);
103
+ }
104
+ return computedValue;
105
+ }
106
+
107
+ /**
108
+ * Computes modulators of a given voice. Source and index indicate what modulators shall be computed
109
+ * @param voice {WorkletVoice} the voice to compute modulators for
110
+ * @param controllerTable {Int16Array} all midi controllers as 14bit values + the non controller indexes, starting at 128
111
+ * @param sourceUsesCC {number} what modulators should be computed, -1 means all, 0 means modulator source enum 1 means midi controller
112
+ * @param sourceIndex {number} enum for the source
113
+ */
114
+ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sourceIndex = 0) {
115
+ const { modulators, generators, modulatedGenerators } = voice;
116
+
117
+ if (sourceUsesCC === -1)
118
+ {
119
+ // All modulators mode: compute all modulators
120
+ modulatedGenerators.set(generators);
121
+ modulators.forEach(mod => {
122
+ modulatedGenerators[mod.modulatorDestination] += computeWorkletModulator(controllerTable, mod, voice);
123
+ });
124
+ recalculateVolumeEnvelope(voice);
125
+ return;
126
+ }
127
+
128
+ // Optimized mode: calculate only modulators that use the given source
129
+ const volenvNeedsRecalculation = new Set([
130
+ generatorTypes.initialAttenuation,
131
+ generatorTypes.delayVolEnv,
132
+ generatorTypes.attackVolEnv,
133
+ generatorTypes.holdVolEnv,
134
+ generatorTypes.decayVolEnv,
135
+ generatorTypes.sustainVolEnv,
136
+ generatorTypes.releaseVolEnv,
137
+ generatorTypes.keyNumToVolEnvHold,
138
+ generatorTypes.keyNumToVolEnvDecay
139
+ ]);
140
+
141
+ const computedDestinations = new Set();
142
+
143
+ modulators.forEach(mod => {
144
+ if (
145
+ (mod.sourceUsesCC === sourceUsesCC && mod.sourceIndex === sourceIndex) ||
146
+ (mod.secSrcUsesCC === sourceUsesCC && mod.secSrcIndex === sourceIndex)
147
+ ) {
148
+ const destination = mod.modulatorDestination;
149
+ if (!computedDestinations.has(destination))
150
+ {
151
+ // Reset this destination
152
+ modulatedGenerators[destination] = generators[destination];
153
+ // Compute all modulators for this destination
154
+ modulators.forEach(m => {
155
+ if (m.modulatorDestination === destination)
156
+ {
157
+ modulatedGenerators[destination] += computeWorkletModulator(controllerTable, m, voice);
158
+ }
159
+ });
160
+ computedDestinations.add(destination);
161
+ }
162
+ }
163
+ });
164
+
165
+ // Recalculate volume envelope if necessary
166
+ if ([...computedDestinations].some(dest => volenvNeedsRecalculation.has(dest)))
167
+ {
168
+ recalculateVolumeEnvelope(voice);
169
+ }
170
+ }
171
+
172
+
173
+ /**
174
+ * as follows: transforms[curveType][polarity][direction] is an array
175
+ * @type {Float32Array[][][]}
176
+ */
177
+ const transforms = [];
178
+
179
+ for(let curve = 0; curve < 4; curve++)
180
+ {
181
+ transforms[curve] =
182
+ [
183
+ [
184
+ new Float32Array(MOD_PRECOMPUTED_LENGTH),
185
+ new Float32Array(MOD_PRECOMPUTED_LENGTH)
186
+ ],
187
+ [
188
+ new Float32Array(MOD_PRECOMPUTED_LENGTH),
189
+ new Float32Array(MOD_PRECOMPUTED_LENGTH)
190
+ ]
191
+ ];
192
+ for (let i = 0; i < MOD_PRECOMPUTED_LENGTH; i++) {
193
+
194
+ // polarity 0 dir 0
195
+ transforms[curve][0][0][i] = getModulatorCurveValue(
196
+ 0,
197
+ curve,
198
+ i / MOD_PRECOMPUTED_LENGTH,
199
+ 0);
200
+ if (isNaN(transforms[curve][0][0][i])) {
201
+ transforms[curve][0][0][i] = 1;
202
+ }
203
+
204
+ // polarity 1 dir 0
205
+ transforms[curve][1][0][i] = getModulatorCurveValue(
206
+ 0,
207
+ curve,
208
+ i / MOD_PRECOMPUTED_LENGTH,
209
+ 1);
210
+ if (isNaN(transforms[curve][1][0][i])) {
211
+ transforms[curve][1][0][i] = 1;
212
+ }
213
+
214
+ // polarity 0 dir 1
215
+ transforms[curve][0][1][i] = getModulatorCurveValue(
216
+ 1,
217
+ curve,
218
+ i / MOD_PRECOMPUTED_LENGTH,
219
+ 0);
220
+ if (isNaN(transforms[curve][0][1][i])) {
221
+ transforms[curve][0][1][i] = 1;
222
+ }
223
+
224
+ // polarity 1 dir 1
225
+ transforms[curve][1][1][i] = getModulatorCurveValue(
226
+ 1,
227
+ curve,
228
+ i / MOD_PRECOMPUTED_LENGTH,
229
+ 1);
230
+ if (isNaN(transforms[curve][1][1][i])) {
231
+ transforms[curve][1][1][i] = 1;
232
+ }
233
+ }
234
+ }