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,75 @@
1
+ import { writeDword, writeWord } from '../../utils/byte_functions/little_endian.js'
2
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
4
+ import { generatorTypes } from '../read/generators.js'
5
+
6
+ /**
7
+ * @this {SoundFont2}
8
+ * @returns {IndexedByteArray}
9
+ */
10
+ export function getIGEN()
11
+ {
12
+ // go through all instruments -> zones and write generators sequentially (add 4 for terminal)
13
+ let igensize = 4;
14
+ for(const inst of this.instruments)
15
+ {
16
+ igensize += inst.instrumentZones.reduce((sum, z) => {
17
+ // clear sample and range generators before derermining the size
18
+ z.generators = z.generators.filter(g =>
19
+ g.generatorType !== generatorTypes.sampleID &&
20
+ g.generatorType !== generatorTypes.keyRange &&
21
+ g.generatorType !== generatorTypes.velRange
22
+ );
23
+ // add sample and ranges if needed
24
+ // unshift vel then key ( to make key first) and instrument is last
25
+ if(z.velRange.max !== 127 || z.velRange.min !== 0)
26
+ {
27
+ z.generators.unshift({
28
+ generatorType: generatorTypes.velRange,
29
+ generatorValue: z.velRange.max << 8 | z.velRange.min
30
+ });
31
+ }
32
+ if(z.keyRange.max !== 127 || z.keyRange.min !== 0)
33
+ {
34
+ z.generators.unshift({
35
+ generatorType: generatorTypes.keyRange,
36
+ generatorValue: z.keyRange.max << 8 | z.keyRange.min
37
+ });
38
+ }
39
+ if(!z.isGlobal)
40
+ {
41
+ // write sample
42
+ z.generators.push({
43
+ generatorType: generatorTypes.sampleID,
44
+ generatorValue: this.samples.indexOf(z.sample)
45
+ });
46
+ }
47
+ return z.generators.length * 4 + sum;
48
+ }, 0);
49
+ }
50
+ const igendata = new IndexedByteArray(igensize);
51
+ let igenIndex = 0;
52
+ for(const instrument of this.instruments)
53
+ {
54
+ for (const instrumentZone of instrument.instrumentZones)
55
+ {
56
+ // set the start index here
57
+ instrumentZone.generatorZoneStartIndex = igenIndex;
58
+ for (const gen of instrumentZone.generators)
59
+ {
60
+ // name is deceptive, it works on negatives
61
+ writeWord(igendata, gen.generatorType);
62
+ writeWord(igendata, gen.generatorValue);
63
+ igenIndex++;
64
+ }
65
+ }
66
+ }
67
+ // terminal generator, is zero
68
+ writeDword(igendata, 0);
69
+
70
+ return writeRIFFChunk(new RiffChunk(
71
+ "igen",
72
+ igendata.length,
73
+ igendata
74
+ ));
75
+ }
@@ -0,0 +1,46 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeLittleEndian, writeWord } from '../../utils/byte_functions/little_endian.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
4
+
5
+ /**
6
+ * @this {SoundFont2}
7
+ * @returns {IndexedByteArray}
8
+ */
9
+ export function getIMOD()
10
+ {
11
+ // very similar to igen
12
+ // go through all instruments -> zones and write modulators sequentially
13
+ let imodsize = 10;
14
+ for(const inst of this.instruments)
15
+ {
16
+ imodsize += inst.instrumentZones.reduce((sum, z) => z.modulators.length * 10 + sum, 0);
17
+ }
18
+ const imoddata = new IndexedByteArray(imodsize);
19
+ let imodIndex = 0;
20
+ for(const inst of this.instruments)
21
+ {
22
+ for (const ibag of inst.instrumentZones)
23
+ {
24
+ // set the start index here
25
+ ibag.modulatorZoneStartIndex = imodIndex;
26
+ for (const mod of ibag.modulators)
27
+ {
28
+ writeWord(imoddata, mod.modulatorSource);
29
+ writeWord(imoddata, mod.modulatorDestination);
30
+ writeWord(imoddata, mod.transformAmount);
31
+ writeWord(imoddata, mod.modulationSecondarySrc);
32
+ writeWord(imoddata, mod.transformType);
33
+ imodIndex++;
34
+ }
35
+ }
36
+ }
37
+
38
+ // terminal modulator, is zero
39
+ writeLittleEndian(imoddata, 0, 10);
40
+
41
+ return writeRIFFChunk(new RiffChunk(
42
+ "imod",
43
+ imoddata.length,
44
+ imoddata
45
+ ));
46
+ }
@@ -0,0 +1,34 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeStringAsBytes } from '../../utils/byte_functions/string.js'
3
+ import { writeWord } from '../../utils/byte_functions/little_endian.js'
4
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
5
+
6
+ /**
7
+ * @this {SoundFont2}
8
+ * @returns {IndexedByteArray}
9
+ */
10
+ export function getINST()
11
+ {
12
+ const instsize = this.instruments.length * 22 + 22;
13
+ const instdata = new IndexedByteArray(instsize);
14
+ // the instrument start index is adjusted in ibag, simply write it here
15
+ let instrumentStart = 0;
16
+ let instrumentID = 0;
17
+ for(const inst of this.instruments)
18
+ {
19
+ writeStringAsBytes(instdata, inst.instrumentName, 20);
20
+ writeWord(instdata, instrumentStart);
21
+ instrumentStart += inst.instrumentZones.length;
22
+ inst.instrumentID = instrumentID;
23
+ instrumentID++;
24
+ }
25
+ // write EOI
26
+ writeStringAsBytes(instdata, "EOI", 20);
27
+ writeWord(instdata, instrumentStart);
28
+
29
+ return writeRIFFChunk(new RiffChunk(
30
+ "inst",
31
+ instdata.length,
32
+ instdata
33
+ ));
34
+ }
@@ -0,0 +1,39 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeWord } from '../../utils/byte_functions/little_endian.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
4
+
5
+ /**
6
+ * @this {SoundFont2}
7
+ * @returns {IndexedByteArray}
8
+ */
9
+ export function getPBAG()
10
+ {
11
+ // write all pbags with their start indexes as they were changed in getPGEN() and getPMOD()
12
+ const pbagsize = this.presets.reduce((sum, i) => i.presetZones.length * 4 + sum, 4);
13
+ const pbagdata = new IndexedByteArray(pbagsize);
14
+ let zoneID = 0;
15
+ let generatorIndex = 0;
16
+ let modulatorIndex = 0;
17
+ for(const preset of this.presets)
18
+ {
19
+ preset.presetZoneStartIndex = zoneID;
20
+ for(const pbag of preset.presetZones)
21
+ {
22
+ pbag.zoneID = zoneID;
23
+ writeWord(pbagdata, generatorIndex);
24
+ writeWord(pbagdata, modulatorIndex);
25
+ generatorIndex += pbag.generators.length;
26
+ modulatorIndex += pbag.modulators.length;
27
+ zoneID++;
28
+ }
29
+ }
30
+ // write the terminal PBAG
31
+ writeWord(pbagdata, generatorIndex);
32
+ writeWord(pbagdata, modulatorIndex);
33
+
34
+ return writeRIFFChunk(new RiffChunk(
35
+ "pbag",
36
+ pbagdata.length,
37
+ pbagdata
38
+ ));
39
+ }
@@ -0,0 +1,77 @@
1
+ import { writeWord } from '../../utils/byte_functions/little_endian.js'
2
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
4
+ import { generatorTypes } from '../read/generators.js'
5
+
6
+ /**
7
+ * @this {SoundFont2}
8
+ * @returns {IndexedByteArray}
9
+ */
10
+ export function getPGEN()
11
+ {
12
+ // almost identical to igen, except correct instrument instead of sample gen
13
+ // go through all preset zones and write generators sequentially (add 4 for terminal)
14
+ let pgensize = 4;
15
+ for(const preset of this.presets)
16
+ {
17
+ pgensize += preset.presetZones.reduce((size, z) => {
18
+ // clear instrument and range generators before derermining the size
19
+ z.generators = z.generators.filter(g =>
20
+ g.generatorType !== generatorTypes.instrument &&
21
+ g.generatorType !== generatorTypes.keyRange &&
22
+ g.generatorType !== generatorTypes.velRange
23
+ );
24
+ // unshift vel then key and instrument is last
25
+ if(z.velRange.max !== 127 || z.velRange.min !== 0)
26
+ {
27
+ z.generators.unshift({
28
+ generatorType: generatorTypes.velRange,
29
+ generatorValue: z.velRange.max << 8 | z.velRange.min
30
+ });
31
+ }
32
+ if(z.keyRange.max !== 127 || z.keyRange.min !== 0)
33
+ {
34
+ z.generators.unshift({
35
+ generatorType: generatorTypes.keyRange,
36
+ generatorValue: z.keyRange.max << 8 | z.keyRange.min
37
+ });
38
+ }
39
+ if(!z.isGlobal)
40
+ {
41
+ // write instrument
42
+ z.generators.push({
43
+ generatorType: generatorTypes.instrument,
44
+ generatorValue: this.instruments.indexOf(z.instrument)
45
+ });
46
+ }
47
+ return z.generators.length * 4 + size;
48
+ }, 0);
49
+ }
50
+ const pgendata = new IndexedByteArray(pgensize);
51
+ let pgenIndex = 0;
52
+ for (const preset of this.presets)
53
+ {
54
+ for (const presetZone of preset.presetZones)
55
+ {
56
+ // set the start index here
57
+ presetZone.generatorZoneStartIndex = pgenIndex;
58
+ // write generators
59
+ for (const gen of presetZone.generators)
60
+ {
61
+ // name is deceptive, it works on negatives
62
+ writeWord(pgendata, gen.generatorType);
63
+ writeWord(pgendata, gen.generatorValue);
64
+ }
65
+ pgenIndex += presetZone.generators.length;
66
+ }
67
+ }
68
+ // terminal generator, is zero
69
+ writeWord(pgendata, 0);
70
+ writeWord(pgendata, 0);
71
+
72
+ return writeRIFFChunk(new RiffChunk(
73
+ "pgen",
74
+ pgendata.length,
75
+ pgendata
76
+ ));
77
+ }
@@ -0,0 +1,42 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeStringAsBytes } from '../../utils/byte_functions/string.js'
3
+ import { writeDword, writeWord } from '../../utils/byte_functions/little_endian.js'
4
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
5
+
6
+ /**
7
+ * @this {SoundFont2}
8
+ * @returns {IndexedByteArray}
9
+ */
10
+ export function getPHDR()
11
+ {
12
+ const phdrsize = this.presets.length * 38 + 38;
13
+ const phdrdata = new IndexedByteArray(phdrsize);
14
+ // the preset start is adjusted in pbag, this is only for the terminal preset index
15
+ let presetStart = 0;
16
+ for (const preset of this.presets)
17
+ {
18
+ writeStringAsBytes(phdrdata, preset.presetName, 20);
19
+ writeWord(phdrdata, preset.program);
20
+ writeWord(phdrdata, preset.bank);
21
+ writeWord(phdrdata, presetStart);
22
+ // 3 unused dwords, spec says to keep em so we do
23
+ writeDword(phdrdata, preset.library);
24
+ writeDword(phdrdata, preset.genre);
25
+ writeDword(phdrdata, preset.morphology);
26
+ presetStart += preset.presetZones.length;
27
+ }
28
+ // write EOP
29
+ writeStringAsBytes(phdrdata, "EOP", 20);
30
+ writeWord(phdrdata, 0); // program
31
+ writeWord(phdrdata, 0); // bank
32
+ writeWord(phdrdata, presetStart);
33
+ writeDword(phdrdata, 0); // library
34
+ writeDword(phdrdata, 0); // genre
35
+ writeDword(phdrdata, 0); // morphology
36
+
37
+ return writeRIFFChunk(new RiffChunk(
38
+ "phdr",
39
+ phdrdata.length,
40
+ phdrdata
41
+ ));
42
+ }
@@ -0,0 +1,46 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeLittleEndian, writeWord } from '../../utils/byte_functions/little_endian.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
4
+
5
+ /**
6
+ * @this {SoundFont2}
7
+ * @returns {IndexedByteArray}
8
+ */
9
+ export function getPMOD()
10
+ {
11
+ // very similar to imod
12
+ // go through all presets -> zones and write modulators sequentially
13
+ let pmodsize = 10;
14
+ for(const preset of this.presets)
15
+ {
16
+ pmodsize += preset.presetZones.reduce((sum, z) => z.modulators.length * 10 + sum, 0);
17
+ }
18
+ const pmoddata = new IndexedByteArray(pmodsize);
19
+ let pmodIndex = 0;
20
+ for(const preset of this.presets)
21
+ {
22
+ for (const pbag of preset.presetZones)
23
+ {
24
+ // set the start index here
25
+ pbag.modulatorZoneStartIndex = pmodIndex;
26
+ for (const mod of pbag.modulators)
27
+ {
28
+ writeWord(pmoddata, mod.modulatorSource);
29
+ writeWord(pmoddata, mod.modulatorDestination);
30
+ writeWord(pmoddata, mod.transformAmount);
31
+ writeWord(pmoddata, mod.modulationSecondarySrc);
32
+ writeWord(pmoddata, mod.transformType);
33
+ pmodIndex++;
34
+ }
35
+ }
36
+ }
37
+
38
+ // terminal modulator, is zero
39
+ writeLittleEndian(pmoddata, 0, 10);
40
+
41
+ return writeRIFFChunk(new RiffChunk(
42
+ "pmod",
43
+ pmoddata.length,
44
+ pmoddata
45
+ ));
46
+ }
@@ -0,0 +1,72 @@
1
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
2
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
3
+ import { SpessaSynthInfo } from '../../utils/loggin.js'
4
+ import { consoleColors } from '../../utils/other.js'
5
+
6
+ /**
7
+ * @this {SoundFont2}
8
+ * @param smplStartOffsets {number[]}
9
+ * @param smplEndOffsets {number[]}
10
+ * @param compress {boolean}
11
+ * @param quality {number}
12
+ * @param vorbisFunc {EncodeVorbisFunction}
13
+ * @returns {IndexedByteArray}
14
+ */
15
+ export function getSDTA(smplStartOffsets, smplEndOffsets, compress, quality, vorbisFunc)
16
+ {
17
+ // write smpl: write int16 data of each sample linearly
18
+ // get size (calling getAudioData twice doesn't matter since it gets cached)
19
+ const sampleDatas = this.samples.map((s, i) => {
20
+ if(compress)
21
+ {
22
+ s.compressSample(quality, vorbisFunc);
23
+ }
24
+ const r= s.getRawData();
25
+ SpessaSynthInfo(`%cEncoded sample %c${i}. ${s.sampleName}%c of %c${this.samples.length}`,
26
+ consoleColors.info,
27
+ consoleColors.recognized,
28
+ consoleColors.info,
29
+ consoleColors.recognized);
30
+ return r;
31
+ });
32
+ const smplSize = this.samples.reduce((total, s, i) => {
33
+ return total + sampleDatas[i].length + 46;
34
+ }, 0);
35
+ const smplData = new IndexedByteArray(smplSize);
36
+ // resample to int16 and write out
37
+ this.samples.forEach((sample, i) => {
38
+ const data = sampleDatas[i];
39
+ let startOffset;
40
+ let endOffset;
41
+ let jump = data.length;
42
+ if(sample.isCompressed)
43
+ {
44
+ // sf3 offset is in bytes
45
+ startOffset = smplData.currentIndex;
46
+ endOffset = startOffset + data.length;
47
+ }
48
+ else
49
+ {
50
+ // sf2 in sample data points
51
+ startOffset = smplData.currentIndex / 2;
52
+ endOffset = startOffset + data.length / 2;
53
+ jump += 46;
54
+ }
55
+ smplStartOffsets.push(startOffset);
56
+ smplData.set(data, smplData.currentIndex);
57
+ smplData.currentIndex += jump;
58
+ smplEndOffsets.push(endOffset);
59
+ });
60
+
61
+ const smplChunk = writeRIFFChunk(new RiffChunk(
62
+ "smpl",
63
+ smplData.length,
64
+ smplData
65
+ ), new IndexedByteArray([115, 100, 116, 97])); // `sdta`
66
+
67
+ return writeRIFFChunk(new RiffChunk(
68
+ "LIST",
69
+ smplChunk.length,
70
+ smplChunk
71
+ ));
72
+ }
@@ -0,0 +1,54 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeStringAsBytes } from '../../utils/byte_functions/string.js'
3
+ import { writeDword, writeWord } from '../../utils/byte_functions/little_endian.js'
4
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
5
+
6
+ /**
7
+ * @this {SoundFont2}
8
+ * @param smplStartOffsets {number[]}
9
+ * @param smplEndOffsets {number[]}
10
+ * @returns {IndexedByteArray}
11
+ */
12
+ export function getSHDR(smplStartOffsets, smplEndOffsets)
13
+ {
14
+ const sampleLength = 46;
15
+ const shdrData = new IndexedByteArray(sampleLength * (this.samples.length + 1 )); // +1 because EOP
16
+ this.samples.forEach((sample, index) => {
17
+ // sample name
18
+ writeStringAsBytes(shdrData, sample.sampleName, 20);
19
+ // start offset
20
+ const dwStart = smplStartOffsets[index];
21
+ writeDword(shdrData, dwStart);
22
+ // end offset
23
+ const dwEnd = smplEndOffsets[index];
24
+ writeDword(shdrData, dwEnd);
25
+ // loop is stored as relative in sample points, change it to absolute sample points here
26
+ let loopStart = sample.sampleLoopStartIndex / 2 + dwStart;
27
+ let loopEnd = sample.sampleLoopEndIndex / 2 + dwStart;
28
+ if(sample.isCompressed)
29
+ {
30
+ // https://github.com/FluidSynth/fluidsynth/wiki/SoundFont3Format
31
+ loopStart -= dwStart;
32
+ loopEnd -= dwStart;
33
+ }
34
+ writeDword(shdrData, loopStart);
35
+ writeDword(shdrData, loopEnd);
36
+ // sample rate
37
+ writeDword(shdrData, sample.sampleRate);
38
+ // pitch and correction
39
+ shdrData[shdrData.currentIndex++] = sample.samplePitch;
40
+ shdrData[shdrData.currentIndex++] = sample.samplePitchCorrection;
41
+ // sample link
42
+ writeWord(shdrData, sample.sampleLink);
43
+ // sample type: write raw because we simply copy compressed samples
44
+ writeWord(shdrData, sample.sampleType);
45
+ });
46
+
47
+ // write EOS and zero everything else
48
+ writeStringAsBytes(shdrData, "EOS", sampleLength);
49
+ return writeRIFFChunk(new RiffChunk(
50
+ "shdr",
51
+ shdrData.length,
52
+ shdrData
53
+ ));
54
+ }
@@ -0,0 +1,169 @@
1
+ import { consoleColors } from '../../utils/other.js'
2
+ import {
3
+ SpessaSynthGroup,
4
+ SpessaSynthGroupCollapsed,
5
+ SpessaSynthGroupEnd,
6
+ SpessaSynthInfo,
7
+ } from '../../utils/loggin.js'
8
+ import { getUsedProgramsAndKeys } from '../../midi_parser/used_keys_loaded.js'
9
+
10
+ /**
11
+ * @param soundfont {SoundFont2}
12
+ * @param mid {MIDI}
13
+ * @returns {Uint8Array}
14
+ */
15
+ export function trimSoundfont(soundfont, mid)
16
+ {
17
+ /**
18
+ * @param instrument {Instrument}
19
+ * @param combos {{key: number, velocity: number}[]}
20
+ * @returns {number}
21
+ */
22
+ function trimInstrumentZones(instrument, combos)
23
+ {
24
+ let trimmedIZones = 0;
25
+ for (let iZoneIndex = 0; iZoneIndex < instrument.instrumentZones.length; iZoneIndex++)
26
+ {
27
+ const iZone = instrument.instrumentZones[iZoneIndex];
28
+ if(iZone.isGlobal)
29
+ {
30
+ continue;
31
+ }
32
+ const iKeyRange = iZone.keyRange;
33
+ const iVelRange = iZone.velRange;
34
+ let isIZoneUsed = false;
35
+ for(const iCombo of combos)
36
+ {
37
+ if(
38
+ (iCombo.key >= iKeyRange.min && iCombo.key <= iKeyRange.max) &&
39
+ (iCombo.velocity >= iVelRange.min && iCombo.velocity <= iVelRange.max)
40
+ )
41
+ {
42
+ isIZoneUsed = true;
43
+ break;
44
+ }
45
+ }
46
+ if(!isIZoneUsed)
47
+ {
48
+ SpessaSynthInfo(`%c${iZone.sample.sampleName} %cremoved from %c${instrument.instrumentName}%c. Use count: %c${iZone.useCount - 1}`,
49
+ consoleColors.recognized,
50
+ consoleColors.info,
51
+ consoleColors.recognized,
52
+ consoleColors.info,
53
+ consoleColors.recognized);
54
+ if(instrument.safeDeleteZone(iZoneIndex))
55
+ {
56
+ trimmedIZones++;
57
+ iZoneIndex--;
58
+ SpessaSynthInfo(`%c${iZone.sample.sampleName} %cdeleted`,
59
+ consoleColors.recognized,
60
+ consoleColors.info)
61
+ }
62
+ if(iZone.sample.useCount < 1)
63
+ {
64
+ soundfont.deleteSample(iZone.sample);
65
+ }
66
+ }
67
+
68
+ }
69
+ return trimmedIZones;
70
+ }
71
+
72
+ SpessaSynthGroup("%cTrimming soundfont...",
73
+ consoleColors.info);
74
+ const usedProgramsAndKeys = getUsedProgramsAndKeys(mid, soundfont);
75
+
76
+ SpessaSynthGroupCollapsed("%cModifying soundfont...",
77
+ consoleColors.info);
78
+ SpessaSynthInfo("Detected keys for midi:", usedProgramsAndKeys);
79
+ // modify the soundfont to only include programs and samples that are used
80
+ for (let presetIndex = 0; presetIndex < soundfont.presets.length; presetIndex++)
81
+ {
82
+ const p = soundfont.presets[presetIndex];
83
+ const string = p.bank + ":" + p.program;
84
+ const used = usedProgramsAndKeys[string];
85
+ if(used === undefined)
86
+ {
87
+ SpessaSynthInfo(`%cDeleting preset %c${p.presetName}%c and its zones`,
88
+ consoleColors.info,
89
+ consoleColors.recognized,
90
+ consoleColors.info
91
+ );
92
+ soundfont.deletePreset(p);
93
+ presetIndex--;
94
+ }
95
+ else
96
+ {
97
+ const combos = [...used].map(s => {
98
+ const split = s.split("-");
99
+ return {
100
+ key: parseInt(split[0]),
101
+ velocity: parseInt(split[1])
102
+ }
103
+ });
104
+ SpessaSynthGroupCollapsed(`%cTrimming %c${p.presetName}`,
105
+ consoleColors.info,
106
+ consoleColors.recognized);
107
+ SpessaSynthInfo(`Keys for ${p.presetName}:`, combos)
108
+ let trimmedZones = 0;
109
+ // clean the preset to only use zones that are used
110
+ for (let zoneIndex = 0; zoneIndex < p.presetZones.length; zoneIndex++)
111
+ {
112
+ const zone = p.presetZones[zoneIndex];
113
+ if(zone.isGlobal)
114
+ {
115
+ continue;
116
+ }
117
+ const keyRange = zone.keyRange;
118
+ const velRange = zone.velRange;
119
+ // check if any of the combos matches the zone
120
+ let isZoneUsed = false;
121
+ for(const combo of combos)
122
+ {
123
+ if(
124
+ (combo.key >= keyRange.min && combo.key <= keyRange.max) &&
125
+ (combo.velocity >= velRange.min && combo.velocity <= velRange.max)
126
+ )
127
+ {
128
+ // zone is used, trim the instrument zones
129
+ isZoneUsed = true;
130
+ const trimmedIZones = trimInstrumentZones(zone.instrument, combos);
131
+ SpessaSynthInfo(`%cTrimmed off %c${trimmedIZones}%c zones from %c${zone.instrument.instrumentName}`,
132
+ consoleColors.info,
133
+ consoleColors.recognized,
134
+ consoleColors.info,
135
+ consoleColors.recognized
136
+ );
137
+ break;
138
+ }
139
+ }
140
+ if(!isZoneUsed)
141
+ {
142
+ trimmedZones++;
143
+ p.deleteZone(zoneIndex);
144
+ if(zone.instrument.useCount < 1)
145
+ {
146
+ soundfont.deleteInstrument(zone.instrument);
147
+ }
148
+ zoneIndex--;
149
+ }
150
+ }
151
+ SpessaSynthInfo(`%cTrimmed off %c${trimmedZones}%c zones from %c${p.presetName}`,
152
+ consoleColors.info,
153
+ consoleColors.recognized,
154
+ consoleColors.info,
155
+ consoleColors.recognized
156
+ );
157
+ SpessaSynthGroupEnd();
158
+ }
159
+ }
160
+ soundfont.removeUnusedElements();
161
+
162
+ soundfont.soundFontInfo['ICMT'] = `NOTE: This soundfont was trimmed by SpessaSynth to only contain presets used in "${mid.midiName}"\n\n`
163
+ + soundfont.soundFontInfo['ICMT'];
164
+
165
+ SpessaSynthInfo("%cSoundfont modified!",
166
+ consoleColors.recognized)
167
+ SpessaSynthGroupEnd();
168
+ SpessaSynthGroupEnd();
169
+ }