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,234 @@
1
+ import {IndexedByteArray} from "../utils/indexed_array.js";
2
+
3
+ /**
4
+ * midi_message.js
5
+ * purpose: contains enums for midi events and controllers and functions to parse them
6
+ */
7
+
8
+ export class MidiMessage
9
+ {
10
+ /**
11
+ * @param ticks {number}
12
+ * @param byte {number} the message status byte
13
+ * @param data {IndexedByteArray}
14
+ */
15
+ constructor(ticks, byte, data) {
16
+ // absolute ticks from the start
17
+ this.ticks = ticks;
18
+ // message status byte (for meta it's the second byte)
19
+ this.messageStatusByte = byte;
20
+ /**
21
+ * @type {IndexedByteArray}
22
+ */
23
+ this.messageData = data;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Gets the status byte's channel
29
+ * @param statusByte
30
+ * @returns {number} channel is -1 for system messages -2 for meta and -3 for sysex
31
+ */
32
+ export function getChannel(statusByte) {
33
+ const eventType = statusByte & 0xF0;
34
+ const channel = statusByte & 0x0F;
35
+
36
+ let resultChannel = channel;
37
+
38
+ switch (eventType) {
39
+ // midi (and meta and sysex headers)
40
+ case 0x80:
41
+ case 0x90:
42
+ case 0xA0:
43
+ case 0xB0:
44
+ case 0xC0:
45
+ case 0xD0:
46
+ case 0xE0:
47
+ break;
48
+
49
+ case 0xF0:
50
+ switch (channel) {
51
+ case 0x0:
52
+ resultChannel = -3;
53
+ break;
54
+
55
+ case 0x1:
56
+ case 0x2:
57
+ case 0x3:
58
+ case 0x4:
59
+ case 0x5:
60
+ case 0x6:
61
+ case 0x7:
62
+ case 0x8:
63
+ case 0x9:
64
+ case 0xA:
65
+ case 0xB:
66
+ case 0xC:
67
+ case 0xD:
68
+ case 0xE:
69
+ resultChannel = -1;
70
+ break;
71
+
72
+ case 0xF:
73
+ resultChannel = -2;
74
+ break;
75
+ }
76
+ break;
77
+
78
+ default:
79
+ resultChannel = -1;
80
+ }
81
+
82
+ return resultChannel;
83
+ }
84
+
85
+ // all the midi statuses dictionary
86
+ export const messageTypes = {
87
+ noteOff: 0x80,
88
+ noteOn: 0x90,
89
+ polyPressure: 0xA0,
90
+ controllerChange: 0xB0,
91
+ programChange: 0xC0,
92
+ channelPressure: 0xD0,
93
+ pitchBend: 0xE0,
94
+ systemExclusive: 0xF0,
95
+ timecode: 0xF1,
96
+ songPosition: 0xF2,
97
+ songSelect: 0xF3,
98
+ tuneRequest: 0xF6,
99
+ clock: 0xF8,
100
+ start: 0xFA,
101
+ continue: 0xFB,
102
+ stop: 0xFC,
103
+ activeSensing: 0xFE,
104
+ reset: 0xFF,
105
+ sequenceNumber: 0x00,
106
+ text: 0x01,
107
+ copyright: 0x02,
108
+ trackName: 0x03,
109
+ instrumentName: 0x04,
110
+ lyric: 0x05,
111
+ marker: 0x06,
112
+ cuePoint: 0x07,
113
+ midiChannelPrefix: 0x20,
114
+ midiPort: 0x21,
115
+ endOfTrack: 0x2F,
116
+ setTempo: 0x51,
117
+ smpteOffset: 0x54,
118
+ timeSignature: 0x58,
119
+ keySignature: 0x59,
120
+ sequenceSpecific: 0x7F
121
+ };
122
+
123
+
124
+ /**
125
+ * Gets the event's status and channel from the status byte
126
+ * @param statusByte {number} the status byte
127
+ * @returns {{channel: number, status: number}} channel will be -1 for sysex and meta
128
+ */
129
+ export function getEvent(statusByte) {
130
+ const status = statusByte & 0xF0;
131
+ const channel = statusByte & 0x0F;
132
+
133
+ let eventChannel = -1;
134
+ let eventStatus = statusByte;
135
+
136
+ if (status >= 0x80 && status <= 0xE0) {
137
+ eventChannel = channel;
138
+ eventStatus = status;
139
+ }
140
+
141
+ return {
142
+ status: eventStatus,
143
+ channel: eventChannel
144
+ };
145
+ }
146
+
147
+
148
+ /**
149
+ * @enum {number}
150
+ */
151
+ export const midiControllers = {
152
+ bankSelect: 0,
153
+ modulationWheel: 1,
154
+ breathController: 2,
155
+ footController: 4,
156
+ portamentoTime: 5,
157
+ dataEntryMsb: 6,
158
+ mainVolume: 7,
159
+ balance: 8,
160
+ pan: 10,
161
+ expressionController: 11,
162
+ effectControl1: 12,
163
+ effectControl2: 13,
164
+ generalPurposeController1: 16,
165
+ generalPurposeController2: 17,
166
+ generalPurposeController3: 18,
167
+ generalPurposeController4: 19,
168
+ lsbForControl0BankSelect: 32,
169
+ lsbForControl1ModulationWheel: 33,
170
+ lsbForControl2BreathController: 34,
171
+ lsbForControl4FootController: 36,
172
+ lsbForControl5PortamentoTime: 37,
173
+ lsbForControl6DataEntry: 38,
174
+ lsbForControl7MainVolume: 39,
175
+ lsbForControl8Balance: 40,
176
+ lsbForControl10Pan: 42,
177
+ lsbForControl11ExpressionController: 43,
178
+ lsbForControl12EffectControl1: 44,
179
+ lsbForControl13EffectControl2: 45,
180
+ sustainPedal: 64,
181
+ portamentoOnOff: 65,
182
+ sostenutoPedal: 66,
183
+ softPedal: 67,
184
+ legatoFootswitch: 68,
185
+ hold2Pedal: 69,
186
+ soundVariation: 70,
187
+ timbreHarmonicContent: 71,
188
+ releaseTime: 72,
189
+ attackTime: 73,
190
+ brightness: 74,
191
+ soundController6: 75,
192
+ soundController7: 76,
193
+ soundController8: 77,
194
+ soundController9: 78,
195
+ soundController10: 79,
196
+ generalPurposeController5: 80,
197
+ generalPurposeController6: 81,
198
+ generalPurposeController7: 82,
199
+ generalPurposeController8: 83,
200
+ portamentoControl: 84,
201
+ effects1Depth: 91,
202
+ effects2Depth: 92,
203
+ effects3Depth: 93,
204
+ effects4Depth: 94,
205
+ effects5Depth: 95,
206
+ dataIncrement: 96,
207
+ dataDecrement: 97,
208
+ NRPNLsb: 98,
209
+ NRPNMsb: 99,
210
+ RPNLsb: 100,
211
+ RPNMsb: 101,
212
+ allSoundOff: 120,
213
+ resetAllControllers: 121,
214
+ localControlOnOff: 122,
215
+ allNotesOff: 123,
216
+ omniModeOff: 124,
217
+ omniModeOn: 125,
218
+ monoModeOn: 126,
219
+ polyModeOn: 127
220
+ };
221
+
222
+
223
+ /**
224
+ * @type {{"11": number, "12": number, "13": number, "14": number, "8": number, "9": number, "10": number}}
225
+ */
226
+ export const dataBytesAmount = {
227
+ 0x8: 2, // note off
228
+ 0x9: 2, // note on
229
+ 0xA: 2, // note at
230
+ 0xB: 2, // cc change
231
+ 0xC: 1, // pg change
232
+ 0xD: 1, // channel aftertouch
233
+ 0xE: 2 // pitch wheel
234
+ };
@@ -0,0 +1,95 @@
1
+ import { messageTypes } from './midi_message.js'
2
+ import { writeVariableLengthQuantity } from '../utils/byte_functions/variable_length_quantity.js'
3
+ import { writeBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js'
4
+
5
+ /**
6
+ * Exports the midi as a .mid file
7
+ * @param midi {MIDI}
8
+ * @returns {Uint8Array} the binary .mid file data
9
+ */
10
+ export function writeMIDIFile(midi)
11
+ {
12
+ /**
13
+ * @type {Uint8Array[]}
14
+ */
15
+ const binaryTrackData = [];
16
+ for(const track of midi.tracks)
17
+ {
18
+ const binaryTrack = [];
19
+ let currentTick = 0;
20
+ let runningByte = undefined;
21
+ for(const event of track)
22
+ {
23
+ // ticks stored in MIDI are absolute, but .mid wants relative. Convert them here.
24
+ const deltaTicks = event.ticks - currentTick;
25
+ /**
26
+ * @type {number[]}
27
+ */
28
+ let messageData;
29
+ // determine the message
30
+ if(event.messageStatusByte <= messageTypes.keySignature || event.messageStatusByte === messageTypes.sequenceSpecific)
31
+ {
32
+ // this is a meta message
33
+ // syntax is FF<type><length><data>
34
+ messageData = [0xff, event.messageStatusByte, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
35
+ }
36
+ else if(event.messageStatusByte === messageTypes.systemExclusive)
37
+ {
38
+ // this is a system exclusive message
39
+ // syntax is F0<length><data>
40
+ messageData = [0xf0, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
41
+ }
42
+ else
43
+ {
44
+ // this is a midi message
45
+ messageData = [];
46
+ if(runningByte !== event.messageStatusByte)
47
+ {
48
+ // running byte was not the byte we want. Add the byte here.
49
+ runningByte = event.messageStatusByte;
50
+ // add the status byte to the midi
51
+ messageData.push(event.messageStatusByte);
52
+ }
53
+ // add the data
54
+ messageData.push(...event.messageData);
55
+ }
56
+ // write VLQ
57
+ binaryTrack.push(...writeVariableLengthQuantity(deltaTicks));
58
+ // write message
59
+ binaryTrack.push(...messageData);
60
+ currentTick += deltaTicks;
61
+ }
62
+ binaryTrackData.push(new Uint8Array(binaryTrack));
63
+ }
64
+
65
+ /**
66
+ * @param text {string}
67
+ * @param arr {number[]}
68
+ */
69
+ function writeText(text, arr)
70
+ {
71
+ for(let i = 0; i < text.length; i++)
72
+ {
73
+ arr.push(text.charCodeAt(i));
74
+ }
75
+ }
76
+
77
+ // write the file
78
+ const binaryData = [];
79
+ // write header
80
+ writeText("MThd", binaryData); // MThd
81
+ binaryData.push(...writeBytesAsUintBigEndian(6, 4)); // length
82
+ binaryData.push(0, midi.format); // format
83
+ binaryData.push(...writeBytesAsUintBigEndian(midi.tracksAmount, 2)); // num tracks
84
+ binaryData.push(...writeBytesAsUintBigEndian(midi.timeDivision, 2)); // time division
85
+
86
+ // write tracks
87
+ for(const track of binaryTrackData)
88
+ {
89
+ // write track header
90
+ writeText("MTrk", binaryData); // MTrk
91
+ binaryData.push(...writeBytesAsUintBigEndian(track.length, 4)); // length
92
+ binaryData.push(...track); // write data
93
+ }
94
+ return new Uint8Array(binaryData);
95
+ }
@@ -0,0 +1,271 @@
1
+ import { combineArrays, IndexedByteArray } from '../utils/indexed_array.js'
2
+ import { writeMIDIFile } from './midi_writer.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../soundfont/read/riff_chunk.js'
4
+ import { getStringBytes } from '../utils/byte_functions/string.js'
5
+ import { messageTypes, midiControllers, MidiMessage } from './midi_message.js'
6
+ import { DEFAULT_PERCUSSION } from '../synthetizer/synthetizer.js'
7
+ import { getGsOn } from './midi_editor.js'
8
+ import { SpessaSynthInfo } from '../utils/loggin.js'
9
+ import { consoleColors } from '../utils/other.js'
10
+
11
+ /**
12
+ *
13
+ * @param soundfontBinary {Uint8Array}
14
+ * @param mid {MIDI}
15
+ * @param soundfont {SoundFont2}
16
+ * @returns {IndexedByteArray}
17
+ */
18
+ export function writeRMIDI(soundfontBinary, mid, soundfont)
19
+ {
20
+ // add 1 to bank. See wiki About-RMIDI
21
+ // also fix presets that don't exists since midiplayer6 doesn't seem to default to 0 when nonextistent...
22
+ let system = "gm";
23
+ /**
24
+ * The unwanted system messages such as gm/gm2 on
25
+ * @type {{tNum: number, e: MidiMessage}[]}
26
+ */
27
+ let unwantedSystems = [];
28
+ mid.tracks.forEach((t, trackNum) => {
29
+ /**
30
+ * @type {boolean[]}
31
+ */
32
+ let hasBankSelects = Array(16).fill(true);
33
+ mid.usedChannelsOnTrack[trackNum].forEach(c => {
34
+ // fill with true, only the channels on this track are set to false
35
+ // (so we won't add banks for channels the track isn't refering to)
36
+ hasBankSelects[c] = false;
37
+ });
38
+ /**
39
+ * @type {MidiMessage[]}
40
+ */
41
+ let lastBankChanges = [];
42
+ /**
43
+ * @type {boolean[]}
44
+ */
45
+ let drums = Array(16).fill(false);
46
+ drums[DEFAULT_PERCUSSION] = true;
47
+ let programs = Array(16).fill(0);
48
+ t.forEach(e => {
49
+ const status = e.messageStatusByte & 0xF0;
50
+ if(
51
+ status !== messageTypes.controllerChange &&
52
+ status !== messageTypes.programChange &&
53
+ status !== messageTypes.systemExclusive
54
+ )
55
+ {
56
+ return;
57
+ }
58
+
59
+ if(status === messageTypes.systemExclusive)
60
+ {
61
+ // check for drum sysex
62
+ if(
63
+ e.messageData[0] !== 0x41 || // roland
64
+ e.messageData[2] !== 0x42 || // GS
65
+ e.messageData[3] !== 0x12 || // GS
66
+ e.messageData[4] !== 0x40 || // system parameter
67
+ (e.messageData[5] & 0x10 ) === 0 || // part parameter
68
+ e.messageData[6] !== 0x15 // drum part
69
+ )
70
+ {
71
+ // check for XG
72
+ if(
73
+ e.messageData[0] === 0x43 && // yamaha
74
+ e.messageData[2] === 0x4C && // sXG ON
75
+ e.messageData[5] === 0x7E &&
76
+ e.messageData[6] === 0x00
77
+ )
78
+ {
79
+ system = "xg";
80
+ }
81
+ else
82
+ if(
83
+ e.messageData[0] === 0x41 // roland
84
+ && e.messageData[2] === 0x42 // GS
85
+ && e.messageData[6] === 0x7F // Mode set
86
+ )
87
+ {
88
+ system = "gs";
89
+ }
90
+ else
91
+ if(
92
+ e.messageData[0] === 0x7E // non realtime
93
+ && e.messageData[2] === 0x09 // gm system
94
+ )
95
+ {
96
+ system = "gm";
97
+ unwantedSystems.push({
98
+ tNum: trackNum,
99
+ e: e
100
+ });
101
+ }
102
+ return;
103
+ }
104
+ const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][e.messageData[5] & 0x0F];
105
+ drums[sysexChannel] = !!(e.messageData[7] > 0 && e.messageData[5] >> 4);
106
+ return;
107
+ }
108
+
109
+ const chNum = e.messageStatusByte & 0xF;
110
+ if(status === messageTypes.programChange)
111
+ {
112
+ // check if the preset for this program exists
113
+ if(drums[chNum])
114
+ {
115
+ if(soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank === 128) === -1)
116
+ {
117
+ e.messageData[0] = soundfont.presets.find(p => p.bank === 128)?.program || 0;
118
+ }
119
+ }
120
+ else
121
+ {
122
+ if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank !== 128) === -1)
123
+ {
124
+ e.messageData[0] = soundfont.presets.find(p => p.bank !== 128)?.program || 0;
125
+ }
126
+ }
127
+ programs[e.messageStatusByte & 0xf] = e.messageData[0];
128
+ // check if this preset exists for program and bank
129
+ const bank = lastBankChanges[chNum]?.messageData[1];
130
+ if(bank === undefined)
131
+ {
132
+ return;
133
+ }
134
+ if(system === "xg" && drums[chNum])
135
+ {
136
+ // drums override: set bank to 127
137
+ lastBankChanges[chNum].messageData[1] = 127;
138
+ return;
139
+ }
140
+
141
+ if(soundfont.presets.findIndex(p => p.bank === bank && p.program === e.messageData[0]) === -1)
142
+ {
143
+ // no preset with this bank. set to 1 (0)
144
+ lastBankChanges[chNum].messageData[1] = 1;
145
+ SpessaSynthInfo(`%cNo preset %c${bank}:${e.messageData[0]}%c. Changing bank to 1.`,
146
+ consoleColors.info,
147
+ consoleColors.recognized,
148
+ consoleColors.info);
149
+ }
150
+ else
151
+ {
152
+ // there is a preset with this bank. add 1
153
+ lastBankChanges[chNum].messageData[1]++;
154
+ SpessaSynthInfo(`%cPreset %c${bank}:${e.messageData[0]}%c exists. Changing bank to ${lastBankChanges[chNum].messageData[1]}.`,
155
+ consoleColors.info,
156
+ consoleColors.recognized,
157
+ consoleColors.info);
158
+ }
159
+ return;
160
+ }
161
+ if(e.messageData[0] !== midiControllers.bankSelect)
162
+ {
163
+ return;
164
+ }
165
+ // bank select
166
+ hasBankSelects[chNum] = true;
167
+ if(system === "xg")
168
+ {
169
+ // check for xg drums
170
+ drums[chNum] = e.messageData[1] === 120 || e.messageData[1] === 126 || e.messageData[1] === 127;
171
+ }
172
+ lastBankChanges[chNum] = e;
173
+ });
174
+ // add all bank selects that are missing for this track
175
+ hasBankSelects.forEach((has, ch) => {
176
+ if(has === true)
177
+ {
178
+ return;
179
+ }
180
+ // find first program change (for the given channel)
181
+ const status = messageTypes.programChange | ch;
182
+ let indexToAdd = t.findIndex(e => e.messageStatusByte === status);
183
+ if(indexToAdd === -1)
184
+ {
185
+ // no program change...
186
+ // add programs if they are missing from the track (need them to activate bank 1 for the embedded sfont)
187
+ const programIndex = t.findIndex(e => (e.messageStatusByte > 0x80 && e.messageStatusByte < 0xF0) && (e.messageStatusByte & 0xF) === ch);
188
+ if(programIndex === -1)
189
+ {
190
+ // no voices??? skip
191
+ return;
192
+ }
193
+ const programTicks = t[programIndex].ticks;
194
+ const targetProgram = soundfont.getPreset(0, 0).program;
195
+ t.splice(programIndex, 0, new MidiMessage(
196
+ programTicks,
197
+ messageTypes.programChange | ch,
198
+ new IndexedByteArray([targetProgram])
199
+ ));
200
+ indexToAdd = programIndex;
201
+ }
202
+ const ticks = t[indexToAdd].ticks;
203
+ const targetBank = (soundfont.getPreset(0, programs[ch])?.bank + 1) || 1;
204
+ t.splice(indexToAdd,0, new MidiMessage(
205
+ ticks,
206
+ messageTypes.controllerChange | ch,
207
+ new IndexedByteArray([midiControllers.bankSelect, targetBank])
208
+ ));
209
+ });
210
+ });
211
+ // make sure to put xg if gm
212
+ if(system !== "gs" && system !== "xg")
213
+ {
214
+ for(const m of unwantedSystems)
215
+ {
216
+ mid.tracks[m.tNum].splice(mid.tracks[m.tNum].indexOf(m.e), 1);
217
+ }
218
+ let index = 0;
219
+ if(mid.tracks[0][0].messageStatusByte === messageTypes.trackName)
220
+ index++;
221
+ mid.tracks[0].splice(index, 0, getGsOn(0));
222
+ }
223
+ const newMid = new IndexedByteArray(writeMIDIFile(mid).buffer);
224
+
225
+ // infodata for MidiPlayer6
226
+ const today = new Date().toLocaleString();
227
+ const infodata = combineArrays([
228
+ writeRIFFChunk(
229
+ new RiffChunk(
230
+ "ICOP",
231
+ 11,
232
+ getStringBytes("SpessaSynth")
233
+ ),
234
+ new IndexedByteArray([73, 78, 70, 79]) // "INFO"
235
+ ),
236
+ writeRIFFChunk(
237
+ new RiffChunk(
238
+ "INAM",
239
+ mid.rawMidiName.length,
240
+ new IndexedByteArray(mid.rawMidiName.buffer)
241
+ ),
242
+ ),
243
+ writeRIFFChunk(
244
+ new RiffChunk(
245
+ "ICRD",
246
+ today.length,
247
+ getStringBytes(today)
248
+ )
249
+ )
250
+ ]);
251
+
252
+ const rmiddata = combineArrays([
253
+ new Uint8Array([82, 77, 73, 68]), // "RMID"
254
+ writeRIFFChunk(new RiffChunk(
255
+ "data",
256
+ newMid.length, // "data", size, midi binary
257
+ newMid
258
+ )),
259
+ writeRIFFChunk(new RiffChunk(
260
+ "LIST",
261
+ infodata.length,
262
+ infodata
263
+ )),
264
+ soundfontBinary
265
+ ]);
266
+ return writeRIFFChunk(new RiffChunk(
267
+ "RIFF",
268
+ rmiddata.length,
269
+ rmiddata
270
+ ));
271
+ }