spessasynth_lib 3.9.9

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 (148) hide show
  1. package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  2. package/@types/index.d.ts +34 -0
  3. package/@types/midi_handler/midi_handler.d.ts +39 -0
  4. package/@types/midi_handler/web_midi_link.d.ts +12 -0
  5. package/@types/midi_parser/midi_data.d.ts +95 -0
  6. package/@types/midi_parser/midi_editor.d.ts +45 -0
  7. package/@types/midi_parser/midi_loader.d.ts +100 -0
  8. package/@types/midi_parser/midi_message.d.ts +154 -0
  9. package/@types/midi_parser/midi_writer.d.ts +6 -0
  10. package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  11. package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  12. package/@types/sequencer/sequencer.d.ts +180 -0
  13. package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  14. package/@types/soundfont/read/generators.d.ts +98 -0
  15. package/@types/soundfont/read/instruments.d.ts +50 -0
  16. package/@types/soundfont/read/modulators.d.ts +73 -0
  17. package/@types/soundfont/read/presets.d.ts +87 -0
  18. package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  19. package/@types/soundfont/read/samples.d.ts +134 -0
  20. package/@types/soundfont/read/zones.d.ts +141 -0
  21. package/@types/soundfont/soundfont.d.ts +76 -0
  22. package/@types/soundfont/write/ibag.d.ts +6 -0
  23. package/@types/soundfont/write/igen.d.ts +6 -0
  24. package/@types/soundfont/write/imod.d.ts +6 -0
  25. package/@types/soundfont/write/inst.d.ts +6 -0
  26. package/@types/soundfont/write/pbag.d.ts +6 -0
  27. package/@types/soundfont/write/pgen.d.ts +6 -0
  28. package/@types/soundfont/write/phdr.d.ts +6 -0
  29. package/@types/soundfont/write/pmod.d.ts +6 -0
  30. package/@types/soundfont/write/sdta.d.ts +11 -0
  31. package/@types/soundfont/write/shdr.d.ts +8 -0
  32. package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  33. package/@types/soundfont/write/write.d.ts +21 -0
  34. package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  35. package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  36. package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  37. package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  38. package/@types/synthetizer/synthetizer.d.ts +286 -0
  39. package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +88 -0
  40. package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  41. package/@types/synthetizer/worklet_url.d.ts +5 -0
  42. package/@types/utils/buffer_to_wav.d.ts +8 -0
  43. package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  44. package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  45. package/@types/utils/byte_functions/string.d.ts +22 -0
  46. package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  47. package/@types/utils/indexed_array.d.ts +21 -0
  48. package/@types/utils/loggin.d.ts +26 -0
  49. package/@types/utils/other.d.ts +32 -0
  50. package/LICENSE +26 -0
  51. package/README.md +83 -0
  52. package/externals/NOTICE +9 -0
  53. package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  54. package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  55. package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  56. package/externals/stbvorbis_sync/LICENSE +202 -0
  57. package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  58. package/index.js +73 -0
  59. package/midi_handler/README.md +3 -0
  60. package/midi_handler/midi_handler.js +118 -0
  61. package/midi_handler/web_midi_link.js +41 -0
  62. package/midi_parser/README.md +3 -0
  63. package/midi_parser/midi_data.js +121 -0
  64. package/midi_parser/midi_editor.js +557 -0
  65. package/midi_parser/midi_loader.js +502 -0
  66. package/midi_parser/midi_message.js +234 -0
  67. package/midi_parser/midi_writer.js +95 -0
  68. package/midi_parser/rmidi_writer.js +271 -0
  69. package/midi_parser/used_keys_loaded.js +172 -0
  70. package/package.json +44 -0
  71. package/sequencer/README.md +23 -0
  72. package/sequencer/sequencer.js +439 -0
  73. package/sequencer/worklet_sequencer/events.js +92 -0
  74. package/sequencer/worklet_sequencer/play.js +309 -0
  75. package/sequencer/worklet_sequencer/process_event.js +167 -0
  76. package/sequencer/worklet_sequencer/process_tick.js +85 -0
  77. package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  78. package/sequencer/worklet_sequencer/song_control.js +193 -0
  79. package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  80. package/soundfont/README.md +8 -0
  81. package/soundfont/read/generators.js +212 -0
  82. package/soundfont/read/instruments.js +125 -0
  83. package/soundfont/read/modulators.js +249 -0
  84. package/soundfont/read/presets.js +300 -0
  85. package/soundfont/read/riff_chunk.js +81 -0
  86. package/soundfont/read/samples.js +398 -0
  87. package/soundfont/read/zones.js +310 -0
  88. package/soundfont/soundfont.js +357 -0
  89. package/soundfont/write/ibag.js +39 -0
  90. package/soundfont/write/igen.js +75 -0
  91. package/soundfont/write/imod.js +46 -0
  92. package/soundfont/write/inst.js +34 -0
  93. package/soundfont/write/pbag.js +39 -0
  94. package/soundfont/write/pgen.js +77 -0
  95. package/soundfont/write/phdr.js +42 -0
  96. package/soundfont/write/pmod.js +46 -0
  97. package/soundfont/write/sdta.js +72 -0
  98. package/soundfont/write/shdr.js +54 -0
  99. package/soundfont/write/soundfont_trimmer.js +169 -0
  100. package/soundfont/write/write.js +180 -0
  101. package/synthetizer/README.md +6 -0
  102. package/synthetizer/audio_effects/effects_config.js +21 -0
  103. package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  104. package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  105. package/synthetizer/audio_effects/reverb.js +24 -0
  106. package/synthetizer/synth_event_handler.js +156 -0
  107. package/synthetizer/synthetizer.js +750 -0
  108. package/synthetizer/worklet_processor.min.js +13 -0
  109. package/synthetizer/worklet_system/README.md +6 -0
  110. package/synthetizer/worklet_system/main_processor.js +363 -0
  111. package/synthetizer/worklet_system/message_protocol/handle_message.js +193 -0
  112. package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  113. package/synthetizer/worklet_system/message_protocol/worklet_message.js +118 -0
  114. package/synthetizer/worklet_system/minify_processor.sh +4 -0
  115. package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  116. package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  117. package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  118. package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  119. package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  120. package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  121. package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  122. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  123. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  124. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  125. package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  126. package/synthetizer/worklet_system/worklet_processor.js +9 -0
  127. package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  128. package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  129. package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  130. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  131. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  132. package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  133. package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  134. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  135. package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  136. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  137. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  138. package/synthetizer/worklet_url.js +5 -0
  139. package/utils/README.md +4 -0
  140. package/utils/buffer_to_wav.js +101 -0
  141. package/utils/byte_functions/big_endian.js +28 -0
  142. package/utils/byte_functions/little_endian.js +74 -0
  143. package/utils/byte_functions/string.js +97 -0
  144. package/utils/byte_functions/variable_length_quantity.js +37 -0
  145. package/utils/encode_vorbis.js +30 -0
  146. package/utils/indexed_array.js +41 -0
  147. package/utils/loggin.js +79 -0
  148. package/utils/other.js +54 -0
@@ -0,0 +1,125 @@
1
+ import {RiffChunk} from "./riff_chunk.js";
2
+ import {InstrumentZone} from "./zones.js";
3
+ import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
4
+ import { readBytesAsString } from '../../utils/byte_functions/string.js'
5
+
6
+ /**
7
+ * instrument.js
8
+ * purpose: parses soundfont instrument and stores them as a class
9
+ */
10
+
11
+ export class Instrument
12
+ {
13
+ /**
14
+ * Creates an instrument
15
+ * @param instrumentChunk {RiffChunk}
16
+ */
17
+ constructor(instrumentChunk)
18
+ {
19
+ this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20).trim();
20
+ this.instrumentZoneIndex = readBytesAsUintLittleEndian(instrumentChunk.chunkData, 2);
21
+ this.instrumentZonesAmount = 0;
22
+ /**
23
+ * @type {InstrumentZone[]}
24
+ */
25
+ this.instrumentZones = [];
26
+ this._useCount = 0;
27
+ }
28
+
29
+ addUseCount()
30
+ {
31
+ this._useCount++;
32
+ this.instrumentZones.forEach(z => z.useCount++);
33
+ }
34
+
35
+ removeUseCount()
36
+ {
37
+ this._useCount--;
38
+ for(let i = 0; i < this.instrumentZones.length; i++)
39
+ {
40
+ if(this.safeDeleteZone(i))
41
+ {
42
+ i--;
43
+ }
44
+ }
45
+ }
46
+
47
+ /**
48
+ * @returns {number}
49
+ */
50
+ get useCount()
51
+ {
52
+ return this._useCount;
53
+ }
54
+
55
+ deleteInstrument()
56
+ {
57
+ this.instrumentZones.forEach(z => z.deleteZone());
58
+ this.instrumentZones.length = 0;
59
+ }
60
+
61
+ /**
62
+ * @param index {number}
63
+ * @returns {boolean} is the zone has been deleted
64
+ */
65
+ safeDeleteZone(index)
66
+ {
67
+ this.instrumentZones[index].useCount--;
68
+ if(this.instrumentZones[index].useCount < 1)
69
+ {
70
+ this.deleteZone(index);
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * @param index {number}
78
+ */
79
+ deleteZone(index)
80
+ {
81
+ this.instrumentZones[index].deleteZone();
82
+ this.instrumentZones.splice(index, 1);
83
+ }
84
+
85
+ /**
86
+ * Loads all the instrument zones, given the amount
87
+ * @param amount {number}
88
+ * @param zones {InstrumentZone[]}
89
+ */
90
+ getInstrumentZones(amount, zones)
91
+ {
92
+ this.instrumentZonesAmount = amount;
93
+ for(let i = this.instrumentZoneIndex; i < this.instrumentZonesAmount + this.instrumentZoneIndex; i++)
94
+ {
95
+ this.instrumentZones.push(zones[i]);
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Reads the instruments
102
+ * @param instrumentChunk {RiffChunk}
103
+ * @param instrumentZones {InstrumentZone[]}
104
+ * @returns {Instrument[]}
105
+ */
106
+ export function readInstruments(instrumentChunk, instrumentZones)
107
+ {
108
+ let instruments = [];
109
+ while(instrumentChunk.chunkData.length > instrumentChunk.chunkData.currentIndex)
110
+ {
111
+ let instrument = new Instrument(instrumentChunk);
112
+ if(instruments.length > 0)
113
+ {
114
+ let instrumentsAmount = instrument.instrumentZoneIndex - instruments[instruments.length - 1].instrumentZoneIndex;
115
+ instruments[instruments.length - 1].getInstrumentZones(instrumentsAmount, instrumentZones);
116
+ }
117
+ instruments.push(instrument);
118
+ }
119
+ if(instruments.length > 1)
120
+ {
121
+ // remove EOI
122
+ instruments.pop();
123
+ }
124
+ return instruments;
125
+ }
@@ -0,0 +1,249 @@
1
+ import {signedInt16, readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
2
+ import { IndexedByteArray } from '../../utils/indexed_array.js';
3
+ import { generatorTypes } from './generators.js'
4
+ import { midiControllers } from '../../midi_parser/midi_message.js'
5
+
6
+ /**
7
+ * modulators.js
8
+ * purpose: parses soundfont modulators and the source enums, also includes the default modulators list
9
+ **/
10
+ export const modulatorSources = {
11
+ noController: 0,
12
+ noteOnVelocity: 2,
13
+ noteOnKeyNum: 3,
14
+ polyPressure: 10,
15
+ channelPressure: 13,
16
+ pitchWheel: 14,
17
+ pitchWheelRange: 16,
18
+ link: 127
19
+ }
20
+
21
+ export const modulatorCurveTypes = {
22
+ linear: 0,
23
+ concave: 1,
24
+ convex: 2,
25
+ switch: 3
26
+ }
27
+
28
+ /**
29
+ *
30
+ * type, polarity, direction
31
+ * @type {Float32Array[][][]}
32
+ */
33
+ export const precomputedTransforms = [];
34
+ for (let i = 0; i < 4; i++) {
35
+ precomputedTransforms.push([[], []]);
36
+ }
37
+
38
+ export class Modulator{
39
+ /**
40
+ * Creates a modulator
41
+ * @param dataArray {IndexedByteArray|{srcEnum: number, secSrcEnum: number, dest:number, amt: number, transform: number}}
42
+ */
43
+ constructor(dataArray) {
44
+ if(dataArray.srcEnum)
45
+ {
46
+ this.modulatorSource = dataArray.srcEnum;
47
+ /**
48
+ * @type {generatorTypes}
49
+ */
50
+ this.modulatorDestination = dataArray.dest;
51
+ this.modulationSecondarySrc = dataArray.secSrcEnum;
52
+ this.transformAmount = dataArray.amt;
53
+ this.transformType = dataArray.transform;
54
+ }
55
+ else
56
+ {
57
+ this.modulatorSource = readBytesAsUintLittleEndian(dataArray, 2);
58
+ this.modulatorDestination = readBytesAsUintLittleEndian(dataArray, 2);
59
+ this.transformAmount = signedInt16(dataArray[dataArray.currentIndex++], dataArray[dataArray.currentIndex++]);
60
+ this.modulationSecondarySrc = readBytesAsUintLittleEndian(dataArray, 2);
61
+ this.transformType = readBytesAsUintLittleEndian(dataArray, 2);
62
+ }
63
+
64
+ if(this.modulatorDestination > 58)
65
+ {
66
+ this.modulatorDestination = -1; // flag as invalid (for linked ones)
67
+ }
68
+
69
+ // decode the source
70
+ this.sourcePolarity = this.modulatorSource >> 9 & 1;
71
+ this.sourceDirection = this.modulatorSource >> 8 & 1;
72
+ this.sourceUsesCC = this.modulatorSource >> 7 & 1;
73
+ this.sourceIndex = this.modulatorSource & 127;
74
+ this.sourceCurveType = this.modulatorSource >> 10 & 3;
75
+
76
+ // decode the secondary source
77
+ this.secSrcPolarity = this.modulationSecondarySrc >> 9 & 1;
78
+ this.secSrcDirection = this.modulationSecondarySrc >> 8 & 1;
79
+ this.secSrcUsesCC = this.modulationSecondarySrc >> 7 & 1;
80
+ this.secSrcIndex = this.modulationSecondarySrc & 127;
81
+ this.secSrcCurveType = this.modulationSecondarySrc >> 10 & 3;
82
+
83
+ //this.precomputeModulatorTransform();
84
+ }
85
+
86
+ /**
87
+ * Sums transform and creates a NEW modulator
88
+ * @param modulator {Modulator}
89
+ * @returns {Modulator}
90
+ */
91
+ sumTransform(modulator)
92
+ {
93
+ return new Modulator({
94
+ srcEnum: this.modulatorSource,
95
+ secSrcEnum: this.modulationSecondarySrc,
96
+ dest: this.modulatorDestination,
97
+ transform: this.transformType,
98
+ amt: this.transformAmount + modulator.transformAmount
99
+ });
100
+ }
101
+
102
+ /**
103
+ * @returns {string}
104
+ */
105
+ debugString()
106
+ {
107
+ function getKeyByValue(object, value)
108
+ {
109
+ return Object.keys(object).find(key => object[key] === value);
110
+ }
111
+
112
+ let sourceString = getKeyByValue(modulatorCurveTypes, this.sourceCurveType);
113
+ sourceString += this.sourcePolarity === 0 ? " unipolar " : " bipolar ";
114
+ sourceString += this.sourceDirection === 0 ? "forwards " : "backwards ";
115
+ if(this.sourceUsesCC)
116
+ {
117
+ sourceString += getKeyByValue(midiControllers, this.sourceIndex);
118
+ }
119
+ else
120
+ {
121
+ sourceString += getKeyByValue(modulatorSources, this.sourceIndex);
122
+ }
123
+
124
+ let secSrcString = getKeyByValue(modulatorCurveTypes, this.secSrcCurveType);
125
+ secSrcString += this.secSrcPolarity === 0 ? " unipolar " : " bipolar ";
126
+ secSrcString += this.secSrcCurveType === 0 ? "forwards " : "backwards ";
127
+ if(this.secSrcUsesCC)
128
+ {
129
+ secSrcString += getKeyByValue(midiControllers, this.secSrcIndex);
130
+ }
131
+ else
132
+ {
133
+ secSrcString += getKeyByValue(modulatorSources, this.secSrcIndex);
134
+ }
135
+ return `Modulator:
136
+ Source: ${sourceString}
137
+ Secondary source: ${secSrcString}
138
+ Destination: ${getKeyByValue(generatorTypes, this.modulatorDestination)}
139
+ Trasform amount: ${this.transformAmount}
140
+ Transform type: ${this.transformType}
141
+ \n\n`;
142
+ }
143
+ }
144
+
145
+ function getModSourceEnum(curveType, polarity, direction, isCC, index)
146
+ {
147
+ return (curveType << 10) | (polarity << 9) | (direction << 8) | (isCC << 7) | index;
148
+ }
149
+
150
+ const DEFAULT_ATTENUATION_MOD_AMOUNT = 960;
151
+ const DEFAULT_ATTENUATION_MOD_CURVE_TYPE = modulatorCurveTypes.concave;
152
+
153
+ export const defaultModulators = [
154
+ // vel to attenuation
155
+ new Modulator({
156
+ srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 0, modulatorSources.noteOnVelocity),
157
+ dest: generatorTypes.initialAttenuation,
158
+ amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
159
+ secSrcEnum: 0x0,
160
+ transform: 0}),
161
+
162
+ // mod wheel to vibrato
163
+ new Modulator({srcEnum: 0x0081, dest: generatorTypes.vibLfoToPitch, amt: 50, secSrcEnum: 0x0, transform: 0}),
164
+
165
+ // vol to attenuation
166
+ new Modulator({
167
+ srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 1, midiControllers.mainVolume),
168
+ dest: generatorTypes.initialAttenuation,
169
+ amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
170
+ secSrcEnum: 0x0,
171
+ transform: 0}),
172
+
173
+ // channel pressure to vibrato
174
+ new Modulator({srcEnum: 0x000D, dest: generatorTypes.vibLfoToPitch, amt: 50, secSrcEnum: 0x0, transform: 0}),
175
+
176
+ // pitch wheel to tuning
177
+ new Modulator({srcEnum: 0x020E, dest: generatorTypes.fineTune, amt: 12700, secSrcEnum: 0x0010, transform: 0}),
178
+
179
+ // pan to uhh, pan
180
+ new Modulator({srcEnum: 0x028A, dest: generatorTypes.pan, amt: 1000, secSrcEnum: 0x0, transform: 0}),
181
+
182
+ // expression to attenuation
183
+ new Modulator({
184
+ srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 1, midiControllers.expressionController),
185
+ dest: generatorTypes.initialAttenuation,
186
+ amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
187
+ secSrcEnum: 0x0,
188
+ transform: 0
189
+ }),
190
+
191
+ // reverb effects to send
192
+ // 1000 to align with the reverbSend (overriding it works anyways)
193
+ new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
194
+
195
+ // chorus effects to send
196
+ new Modulator({srcEnum: 0x00DD, dest: generatorTypes.chorusEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
197
+
198
+ // custom modulators heck yeah
199
+ // poly pressure to vibrato
200
+ new Modulator({
201
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 0, 0, 0, modulatorSources.polyPressure),
202
+ dest: generatorTypes.vibLfoToPitch,
203
+ amt: 50,
204
+ secSrcEnum: 0x0,
205
+ transform: 0
206
+ }),
207
+
208
+ // cc 92 (tremolo) to modLFO volume
209
+ new Modulator({
210
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 0, 0, 1, midiControllers.effects2Depth), /*linear forward unipolar cc 92 */
211
+ dest: generatorTypes.modLfoToVolume,
212
+ amt: 24,
213
+ secSrcEnum: 0x0, // no controller
214
+ transform: 0
215
+ }),
216
+
217
+ // cc 72 (release time) to volEnv release
218
+ new Modulator({
219
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0, 1, midiControllers.releaseTime), // linear forward bipolar cc 72
220
+ dest: generatorTypes.releaseVolEnv,
221
+ amt: 1200,
222
+ secSrcEnum: 0x0, // no controller
223
+ transform: 0
224
+ }),
225
+
226
+ // cc 74 (brightness) to filterFc
227
+ new Modulator({
228
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0 , 1, midiControllers.brightness), // linear forwards bipolar cc 74
229
+ dest: generatorTypes.initialFilterFc,
230
+ amt: 4000,
231
+ secSrcEnum: 0x0, // no controller
232
+ transform: 0
233
+ })
234
+ ];
235
+
236
+ /**
237
+ * Reads the modulator read
238
+ * @param modulatorChunk {RiffChunk}
239
+ * @returns {Modulator[]}
240
+ */
241
+ export function readModulators(modulatorChunk)
242
+ {
243
+ let gens = [];
244
+ while(modulatorChunk.chunkData.length > modulatorChunk.chunkData.currentIndex)
245
+ {
246
+ gens.push(new Modulator(modulatorChunk.chunkData));
247
+ }
248
+ return gens;
249
+ }
@@ -0,0 +1,300 @@
1
+ import {RiffChunk} from "./riff_chunk.js";
2
+ import {PresetZone} from "./zones.js";
3
+ import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
4
+ import {Sample} from "./samples.js";
5
+ import { Generator, generatorTypes } from './generators.js'
6
+ import { defaultModulators } from './modulators.js'
7
+ import { readBytesAsString } from '../../utils/byte_functions/string.js'
8
+
9
+ /**
10
+ * parses soundfont presets, also includes function for getting the generators and samples from midi note and velocity
11
+ */
12
+
13
+ export class Preset {
14
+ /**
15
+ * Creates a preset
16
+ * @param presetChunk {RiffChunk}
17
+ */
18
+ constructor(presetChunk)
19
+ {
20
+ this.presetName = readBytesAsString(presetChunk.chunkData, 20)
21
+ .trim()
22
+ .replace(/\d{3}:\d{3}/, ""); // remove those pesky "000:001"
23
+
24
+ this.program = readBytesAsUintLittleEndian(presetChunk.chunkData, 2);
25
+ this.bank = readBytesAsUintLittleEndian(presetChunk.chunkData, 2);
26
+ this.presetZoneStartIndex = readBytesAsUintLittleEndian(presetChunk.chunkData, 2);
27
+ this.presetZonesAmount = 0;
28
+ /**
29
+ * @type {PresetZone[]}
30
+ */
31
+ this.presetZones = [];
32
+
33
+ /**
34
+ * Stores already found getSamplesAndGenerators for reuse
35
+ * @type {SampleAndGenerators[][][]}
36
+ */
37
+ this.foundSamplesAndGenerators = [];
38
+ for(let i = 0; i < 128; i++)
39
+ {
40
+ this.foundSamplesAndGenerators[i] = [];
41
+ }
42
+
43
+ // read the dwords
44
+ this.library = readBytesAsUintLittleEndian(presetChunk.chunkData, 4);
45
+ this.genre = readBytesAsUintLittleEndian(presetChunk.chunkData, 4);
46
+ this.morphology = readBytesAsUintLittleEndian(presetChunk.chunkData, 4);
47
+ }
48
+
49
+ /**
50
+ * Loads all the preset zones, given the amount
51
+ * @param amount {number}
52
+ * @param zones {PresetZone[]}
53
+ */
54
+ getPresetZones(amount, zones)
55
+ {
56
+ this.presetZonesAmount = amount;
57
+ for (let i = this.presetZoneStartIndex; i < this.presetZonesAmount + this.presetZoneStartIndex; i++)
58
+ {
59
+ this.presetZones.push(zones[i]);
60
+ }
61
+ }
62
+
63
+ deletePreset()
64
+ {
65
+ this.presetZones.forEach(z => z.deleteZone());
66
+ this.presetZones.length = 0;
67
+ }
68
+
69
+ /**
70
+ * @param index {number}
71
+ */
72
+ deleteZone(index)
73
+ {
74
+ this.presetZones[index].deleteZone();
75
+ this.presetZones.splice(index, 1);
76
+ }
77
+
78
+ /**
79
+ * Preloads all samples (async)
80
+ */
81
+ preload(keyMin, keyMax)
82
+ {
83
+ for (let key = keyMin; key < keyMax + 1; key++)
84
+ {
85
+ for (let velocity = 0; velocity < 128; velocity++)
86
+ {
87
+ this.getSamplesAndGenerators(key, velocity).forEach(samandgen => {
88
+ if(!samandgen.sample.isSampleLoaded)
89
+ {
90
+ samandgen.sample.getAudioData();
91
+ }
92
+ })
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Preloads a specific key/velocity combo
99
+ * @param key {number}
100
+ * @param velocity {number}
101
+ */
102
+ preloadSpecific(key, velocity)
103
+ {
104
+ this.getSamplesAndGenerators(key, velocity).forEach(samandgen => {
105
+ if(!samandgen.sample.isSampleLoaded)
106
+ {
107
+ samandgen.sample.getAudioData();
108
+ }
109
+ })
110
+ }
111
+
112
+ /**
113
+ * @typedef {{
114
+ * instrumentGenerators: Generator[],
115
+ * presetGenerators: Generator[],
116
+ * modulators: Modulator[],
117
+ * sample: Sample,
118
+ * sampleID: number,
119
+ * }} SampleAndGenerators
120
+ */
121
+
122
+ /**
123
+ * Returns generatorTranslator and generators for given note
124
+ * @param midiNote {number}
125
+ * @param velocity {number}
126
+ * @returns {SampleAndGenerators[]}
127
+ */
128
+ getSamplesAndGenerators(midiNote, velocity)
129
+ {
130
+ const memorized = this.foundSamplesAndGenerators[midiNote][velocity];
131
+ if(memorized)
132
+ {
133
+ return memorized;
134
+ }
135
+
136
+ function isInRange(min, max, number) {
137
+ return number >= min && number <= max;
138
+ }
139
+
140
+ /**
141
+ * @param mod1 {Modulator}
142
+ * @param mod2 {Modulator}
143
+ * @returns {boolean}
144
+ */
145
+ function identicalMod(mod1, mod2)
146
+ {
147
+ return (mod1.modulatorSource === mod2.modulatorSource)
148
+ && (mod1.modulatorDestination === mod2.modulatorDestination)
149
+ && (mod1.modulationSecondarySrc === mod2.modulationSecondarySrc)
150
+ && (mod1.transformType === mod2.transformType);
151
+ }
152
+
153
+ /**
154
+ * @param main {Generator[]}
155
+ * @param adder {Generator[]}
156
+ */
157
+ function addUnique(main, adder)
158
+ {
159
+ main.push(...adder.filter(g => !main.find(mg => mg.generatorType === g.generatorType)));
160
+ }
161
+
162
+ /**
163
+ * @param main {Modulator[]}
164
+ * @param adder {Modulator[]}
165
+ */
166
+ function addUniqueMods(main, adder)
167
+ {
168
+ main.push(...adder.filter(m => !main.find(mm => identicalMod(m, mm))));
169
+ }
170
+
171
+ /**
172
+ * @type {SampleAndGenerators[]}
173
+ */
174
+ let parsedGeneratorsAndSamples = [];
175
+
176
+ /**
177
+ * global zone is always first, so it or nothing
178
+ * @type {Generator[]}
179
+ */
180
+ let globalPresetGenerators = this.presetZones[0].isGlobal ? [...this.presetZones[0].generators] : [];
181
+
182
+ let globalPresetModulators = this.presetZones[0].isGlobal ? [...this.presetZones[0].modulators] : [];
183
+
184
+ // find the preset zones in range
185
+ let presetZonesInRange = this.presetZones.filter(currentZone =>
186
+ (
187
+ isInRange(currentZone.keyRange.min, currentZone.keyRange.max, midiNote)
188
+ &&
189
+ isInRange(currentZone.velRange.min, currentZone.velRange.max, velocity)
190
+ ) && !currentZone.isGlobal);
191
+
192
+ presetZonesInRange.forEach(zone =>
193
+ {
194
+ let presetGenerators = zone.generators;
195
+ let presetModulators = zone.modulators;
196
+ /**
197
+ * global zone is always first, so it or nothing
198
+ * @type {Generator[]}
199
+ */
200
+ let globalInstrumentGenerators = zone.instrument.instrumentZones[0].isGlobal ? [...zone.instrument.instrumentZones[0].generators] : [];
201
+ let globalInstrumentModulators = zone.instrument.instrumentZones[0].isGlobal ? [...zone.instrument.instrumentZones[0].modulators] : [];
202
+
203
+ let instrumentZonesInRange = zone.instrument.instrumentZones
204
+ .filter(currentZone =>
205
+ (
206
+ isInRange(currentZone.keyRange.min,
207
+ currentZone.keyRange.max,
208
+ midiNote)
209
+ &&
210
+ isInRange(currentZone.velRange.min,
211
+ currentZone.velRange.max,
212
+ velocity)
213
+ ) && !currentZone.isGlobal
214
+ );
215
+
216
+ instrumentZonesInRange.forEach(instrumentZone =>
217
+ {
218
+ let instrumentGenerators = [...instrumentZone.generators];
219
+ let instrumentModulators = [...instrumentZone.modulators];
220
+
221
+ addUnique(presetGenerators, globalPresetGenerators);
222
+ // add the unique global preset generators (local replace global(
223
+
224
+
225
+ // add the unique global instrument generators (local replace global)
226
+ addUnique(instrumentGenerators, globalInstrumentGenerators);
227
+
228
+ addUniqueMods(presetModulators, globalPresetModulators);
229
+ addUniqueMods(instrumentModulators, globalInstrumentModulators);
230
+
231
+ // default mods
232
+ addUniqueMods(instrumentModulators, defaultModulators);
233
+
234
+ /**
235
+ * sum preset modulators to instruments (amount) sf spec page 54
236
+ * @type {Modulator[]}
237
+ */
238
+ const finalModulatorList = [...instrumentModulators];
239
+ for(let i = 0; i < presetModulators.length; i++)
240
+ {
241
+ let mod = presetModulators[i];
242
+ const identicalInstrumentModulator = finalModulatorList.findIndex(m => identicalMod(mod, m));
243
+ if(identicalInstrumentModulator !== -1)
244
+ {
245
+ // sum the amounts (this makes a new modulator because otherwise it would overwrite the one in the soundfont!!!
246
+ finalModulatorList[identicalInstrumentModulator] = finalModulatorList[identicalInstrumentModulator].sumTransform(mod);
247
+ }
248
+ else
249
+ {
250
+ finalModulatorList.push(mod);
251
+ }
252
+ }
253
+
254
+
255
+ // combine both generators and add to the final result
256
+ parsedGeneratorsAndSamples.push({
257
+ instrumentGenerators: instrumentGenerators,
258
+ presetGenerators: presetGenerators,
259
+ modulators: finalModulatorList,
260
+ sample: instrumentZone.sample,
261
+ sampleID: instrumentZone.generators.find(g => g.generatorType === generatorTypes.sampleID).generatorValue
262
+ });
263
+ });
264
+ });
265
+
266
+ // save and return
267
+ this.foundSamplesAndGenerators[midiNote][velocity] = parsedGeneratorsAndSamples;
268
+ return parsedGeneratorsAndSamples;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Reads the presets
274
+ * @param presetChunk {RiffChunk}
275
+ * @param presetZones {PresetZone[]}
276
+ * @returns {Preset[]}
277
+ */
278
+ export function readPresets(presetChunk, presetZones)
279
+ {
280
+ /**
281
+ * @type {Preset[]}
282
+ */
283
+ let presets = [];
284
+ while(presetChunk.chunkData.length > presetChunk.chunkData.currentIndex)
285
+ {
286
+ let preset = new Preset(presetChunk);
287
+ if(presets.length > 0)
288
+ {
289
+ let presetZonesAmount = preset.presetZoneStartIndex - presets[presets.length - 1].presetZoneStartIndex;
290
+ presets[presets.length - 1].getPresetZones(presetZonesAmount, presetZones);
291
+ }
292
+ presets.push(preset);
293
+ }
294
+ // remove EOP
295
+ if (presets.length > 1)
296
+ {
297
+ presets.pop();
298
+ }
299
+ return presets;
300
+ }