spessasynth_core 1.0.0

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 (55) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  2. package/.idea/jsLibraryMappings.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/spessasynth_core.iml +12 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README.md +376 -0
  7. package/index.js +7 -0
  8. package/package.json +34 -0
  9. package/spessasynth_core/midi_parser/README.md +3 -0
  10. package/spessasynth_core/midi_parser/midi_loader.js +381 -0
  11. package/spessasynth_core/midi_parser/midi_message.js +231 -0
  12. package/spessasynth_core/sequencer/sequencer.js +192 -0
  13. package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
  14. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
  15. package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
  16. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
  17. package/spessasynth_core/soundfont/README.md +4 -0
  18. package/spessasynth_core/soundfont/chunk/generators.js +205 -0
  19. package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
  20. package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
  21. package/spessasynth_core/soundfont/chunk/presets.js +264 -0
  22. package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
  23. package/spessasynth_core/soundfont/chunk/samples.js +250 -0
  24. package/spessasynth_core/soundfont/chunk/zones.js +264 -0
  25. package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
  26. package/spessasynth_core/synthetizer/README.md +6 -0
  27. package/spessasynth_core/synthetizer/synthesizer.js +303 -0
  28. package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
  29. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
  30. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
  31. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
  32. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
  33. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
  34. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
  35. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
  36. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  37. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
  38. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  39. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
  40. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  41. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  42. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
  43. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  44. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
  45. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  46. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
  47. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
  48. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
  49. package/spessasynth_core/utils/README.md +4 -0
  50. package/spessasynth_core/utils/buffer_to_wav.js +70 -0
  51. package/spessasynth_core/utils/byte_functions.js +141 -0
  52. package/spessasynth_core/utils/loggin.js +79 -0
  53. package/spessasynth_core/utils/other.js +49 -0
  54. package/spessasynth_core/utils/shiftable_array.js +26 -0
  55. package/spessasynth_core/utils/stbvorbis_sync.js +1877 -0
@@ -0,0 +1,381 @@
1
+ import { dataBytesAmount, getChannel, messageTypes, MidiMessage } from './midi_message.js'
2
+ import {ShiftableByteArray} from "../utils/shiftable_array.js";
3
+ import {
4
+ readByte,
5
+ readBytesAsString,
6
+ readBytesAsUintBigEndian,
7
+ readVariableLengthQuantity
8
+ } from "../utils/byte_functions.js";
9
+ import { arrayToHexString, consoleColors, formatTitle } from '../utils/other.js'
10
+ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from '../utils/loggin.js'
11
+
12
+ /**
13
+ * midi_loader.js
14
+ * purpose: parses a midi file for the seqyencer, including things like marker or CC 2/4 loop detection, copyright detection etc.
15
+ */
16
+ export class MIDI{
17
+ /**
18
+ * Parses a given midi file
19
+ * @param arrayBuffer {Buffer}
20
+ * @param fileName {string} optional, replaces the decoded title if empty
21
+ */
22
+ constructor(arrayBuffer, fileName="") {
23
+ SpessaSynthGroupCollapsed(`%cParsing MIDI File...`, consoleColors.info);
24
+
25
+ const fileByteArray = new ShiftableByteArray(arrayBuffer);
26
+ const headerChunk = this.readMIDIChunk(fileByteArray);
27
+ if(headerChunk.type !== "MThd")
28
+ {
29
+ SpessaSynthGroupEnd();
30
+ throw new SyntaxError(`Invalid MIDI Header! Expected "MThd", got "${headerChunk.type}"`);
31
+ }
32
+
33
+ if(headerChunk.size !== 6)
34
+ {
35
+ SpessaSynthGroupEnd();
36
+ throw new RangeError(`Invalid MIDI header chunk size! Expected 6, got ${headerChunk.size}`);
37
+ }
38
+
39
+ // format (ignore)
40
+ readBytesAsUintBigEndian(headerChunk.data, 2);
41
+ // tracks count
42
+ this.tracksAmount = readBytesAsUintBigEndian(headerChunk.data, 2);
43
+ // time division
44
+ this.timeDivision = readBytesAsUintBigEndian(headerChunk.data, 2);
45
+
46
+ const decoder = new TextDecoder('shift-jis');
47
+
48
+ // read the copyright
49
+ this.copyright = "";
50
+
51
+ /**
52
+ * Contains all the tempo changes in the file. (Ordered from last to first)
53
+ * @type {{
54
+ * ticks: number,
55
+ * tempo: number
56
+ * }[]}
57
+ */
58
+ this.tempoChanges = [{ticks: 0, tempo: 120}];
59
+
60
+ let loopStart = null;
61
+ let loopEnd = null;
62
+
63
+ this.lastVoiceEventTick = 0;
64
+
65
+ /**
66
+ * Midi port numbers for each tracks
67
+ * @type {number[]}
68
+ */
69
+ this.midiPorts = [];
70
+
71
+ /**
72
+ * Read all the tracks
73
+ * @type {MidiMessage[][]}
74
+ */
75
+ this.tracks = [];
76
+ for(let i = 0; i < this.tracksAmount; i++)
77
+ {
78
+ /**
79
+ * @type {MidiMessage[]}
80
+ */
81
+ const track = [];
82
+ const trackChunk = this.readMIDIChunk(fileByteArray);
83
+ this.midiPorts.push(0)
84
+
85
+ if(trackChunk.type !== "MTrk")
86
+ {
87
+ SpessaSynthGroupEnd();
88
+ throw new SyntaxError(`Invalid track header! Expected "MTrk" got "${trackChunk.type}"`);
89
+ }
90
+
91
+ /**
92
+ * MIDI running byte
93
+ * @type {number}
94
+ */
95
+ let runningByte = undefined;
96
+
97
+ let totalTicks = 0;
98
+ // loop until we reach the end of track
99
+ while(trackChunk.data.currentIndex < trackChunk.size)
100
+ {
101
+ totalTicks += readVariableLengthQuantity(trackChunk.data);
102
+
103
+ // check if the status byte is valid (IE. larger than 127)
104
+ const statusByteCheck = trackChunk.data[trackChunk.data.currentIndex];
105
+
106
+ let statusByte;
107
+ // if we have a running byte and the status byte isn't valid
108
+ if(runningByte !== undefined && statusByteCheck < 0x80)
109
+ {
110
+ statusByte = runningByte;
111
+ }
112
+ else if(!runningByte && statusByteCheck < 0x80)
113
+ {
114
+ // if we don't have a running byte and the status byte isn't valid, it's an error.
115
+ SpessaSynthGroupEnd();
116
+ throw new SyntaxError(`Unexpected byte with no running byte. (${statusByteCheck})`);
117
+ }
118
+ else
119
+ {
120
+ // if the status byte is valid, just use that
121
+ statusByte = readByte(trackChunk.data);
122
+ }
123
+ const statusByteChannel = getChannel(statusByte);
124
+
125
+ let eventDataLength;
126
+
127
+ // determine the message's length;
128
+ switch(statusByteChannel)
129
+ {
130
+ case -1:
131
+ // system common/realtime (no length)
132
+ eventDataLength = 0;
133
+ break;
134
+ case -2:
135
+ // meta (the next is the actual status byte)
136
+ statusByte = readByte(trackChunk.data);
137
+ eventDataLength = readVariableLengthQuantity(trackChunk.data);
138
+ break;
139
+ case -3:
140
+ // sysex
141
+ eventDataLength = readVariableLengthQuantity(trackChunk.data);
142
+ break;
143
+ default:
144
+ // voice message
145
+ // get the midi message length
146
+ if(totalTicks > this.lastVoiceEventTick)
147
+ {
148
+ this.lastVoiceEventTick = totalTicks;
149
+ }
150
+ eventDataLength = dataBytesAmount[statusByte >> 4];
151
+ break;
152
+ }
153
+
154
+ // put the event data into the array
155
+ const eventData = new ShiftableByteArray(eventDataLength);
156
+ const messageData = trackChunk.data.slice(trackChunk.data.currentIndex, trackChunk.data.currentIndex + eventDataLength);
157
+ trackChunk.data.currentIndex += eventDataLength;
158
+ eventData.set(messageData, 0);
159
+
160
+ runningByte = statusByte;
161
+
162
+ const message = new MidiMessage(totalTicks, statusByte, eventData);
163
+ track.push(message);
164
+
165
+ // check for tempo change
166
+ if(statusByte === messageTypes.setTempo)
167
+ {
168
+ this.tempoChanges.push({
169
+ ticks: totalTicks,
170
+ tempo: 60000000 / readBytesAsUintBigEndian(messageData, 3)
171
+ });
172
+ }
173
+ else
174
+ // check for loop start (Marker "start")
175
+
176
+ if(statusByte === messageTypes.marker)
177
+ {
178
+ const text = readBytesAsString(eventData, eventData.length).trim().toLowerCase();
179
+ switch (text)
180
+ {
181
+ default:
182
+ break;
183
+
184
+ case "start":
185
+ case "loopstart":
186
+ loopStart = totalTicks;
187
+ break;
188
+
189
+ case "loopend":
190
+ loopEnd = totalTicks;
191
+ }
192
+ eventData.currentIndex = 0;
193
+
194
+ }
195
+ else
196
+ // check for loop (CC 2/4)
197
+ if((statusByte & 0xF0) === messageTypes.controllerChange)
198
+ {
199
+ switch(eventData[0])
200
+ {
201
+ case 2:
202
+ case 116:
203
+ loopStart = totalTicks;
204
+ break;
205
+
206
+ case 4:
207
+ case 117:
208
+ if(loopEnd === null)
209
+ {
210
+ loopEnd = totalTicks;
211
+ }
212
+ else
213
+ {
214
+ // this controller has occured more than once, this means that it doesn't indicate the loop
215
+ loopEnd = 0;
216
+ }
217
+ break;
218
+ }
219
+ }
220
+ else
221
+ // check for midi port
222
+ if(statusByte === messageTypes.midiPort)
223
+ {
224
+ this.midiPorts[i] = eventData[0];
225
+ }
226
+ else
227
+ // check for copyright
228
+ if(statusByte === messageTypes.copyright)
229
+ {
230
+ this.copyright += decoder.decode(eventData) + "\n";
231
+ }
232
+
233
+ // check for embedded copyright (roland SC display sysex) http://www.bandtrax.com.au/sysex.htm
234
+ if(statusByte === messageTypes.systemExclusive)
235
+ {
236
+ // header goes like this: 41 10 45 12 10 00 00
237
+ if(arrayToHexString(messageData.slice(0, 7)).trim() === "41 10 45 12 10 00 00")
238
+ {
239
+ const decoded = decoder.decode(messageData.slice(7, messageData.length - 3)) + "\n";
240
+ this.copyright += decoded;
241
+ SpessaSynthInfo(`%cDecoded Roland SC message! %c${decoded}`,
242
+ consoleColors.recognized,
243
+ consoleColors.value)
244
+ }
245
+ }
246
+ }
247
+ this.tracks.push(track);
248
+ SpessaSynthInfo(`%cParsed %c${this.tracks.length}%c / %c${this.tracksAmount}`,
249
+ consoleColors.info,
250
+ consoleColors.value,
251
+ consoleColors.info,
252
+ consoleColors.value);
253
+ }
254
+
255
+ //this.lastVoiceEventTick = Math.max(...this.tracks.map(track =>
256
+ //track[track.length - 1].ticks));
257
+ const firstNoteOns = [];
258
+ for(const t of this.tracks)
259
+ {
260
+ const firstNoteOn = t.find(e => (e.messageStatusByte & 0xF0) === messageTypes.noteOn);
261
+ if(firstNoteOn)
262
+ {
263
+ firstNoteOns.push(firstNoteOn.ticks);
264
+ }
265
+ }
266
+ this.firstNoteOn = Math.min(...firstNoteOns);
267
+
268
+ SpessaSynthInfo(`%cMIDI file parsed. Total tick time: %c${this.lastVoiceEventTick}`,
269
+ consoleColors.info,
270
+ consoleColors.recognized);
271
+ SpessaSynthGroupEnd();
272
+
273
+ if(loopStart !== null && loopEnd === null)
274
+ {
275
+ // not a loop
276
+ loopStart = this.firstNoteOn;
277
+ loopEnd = this.lastVoiceEventTick;
278
+ }
279
+ else {
280
+ if (loopStart === null) {
281
+ loopStart = this.firstNoteOn;
282
+ }
283
+
284
+ if (loopEnd === null || loopEnd === 0) {
285
+ loopEnd = this.lastVoiceEventTick;
286
+ }
287
+ }
288
+
289
+ /**
290
+ *
291
+ * @type {{start: number, end: number}}
292
+ */
293
+ this.loop = {start: loopStart, end: loopEnd};
294
+
295
+ // get track name
296
+ this.midiName = "";
297
+
298
+ // midi name
299
+ if(this.tracks.length > 1)
300
+ {
301
+ // if more than 1 track and the first track has no notes, just find the first trackName in the first track
302
+ if(this.tracks[0].find(
303
+ message => message.messageStatusByte >= messageTypes.noteOn
304
+ &&
305
+ message.messageStatusByte < messageTypes.systemExclusive
306
+ ) === undefined)
307
+ {
308
+ let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
309
+ if(name)
310
+ {
311
+ this.midiName = decoder.decode(name.messageData);
312
+ }
313
+ }
314
+ }
315
+ else
316
+ {
317
+ // if only 1 track, find the first "track name" event
318
+ let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
319
+ if(name)
320
+ {
321
+ this.midiName = decoder.decode(name.messageData);
322
+ }
323
+ }
324
+
325
+ this.fileName = fileName;
326
+
327
+ // if midiName is "", use the file name
328
+ if(this.midiName.trim().length === 0 && fileName.length > 0)
329
+ {
330
+ this.midiName = formatTitle(fileName);
331
+ }
332
+
333
+ // reverse the tempo changes
334
+ this.tempoChanges.reverse();
335
+
336
+ /**
337
+ * The total playback time, in seconds
338
+ * @type {number}
339
+ */
340
+ this.duration = this._ticksToSeconds(this.lastVoiceEventTick);
341
+ }
342
+
343
+ /**
344
+ * @param fileByteArray {ShiftableByteArray}
345
+ * @returns {{type: string, size: number, data: ShiftableByteArray}}
346
+ */
347
+ readMIDIChunk(fileByteArray)
348
+ {
349
+ const chunk = {};
350
+ // type
351
+ chunk.type = readBytesAsString(fileByteArray, 4);
352
+ // size
353
+ chunk.size = readBytesAsUintBigEndian(fileByteArray, 4);
354
+ // data
355
+ chunk.data = new ShiftableByteArray(chunk.size);
356
+ const dataSlice = fileByteArray.slice(fileByteArray.currentIndex, fileByteArray.currentIndex + chunk.size);
357
+ chunk.data.set(dataSlice, 0);
358
+ fileByteArray.currentIndex += chunk.size;
359
+ return chunk;
360
+ }
361
+
362
+
363
+ /**
364
+ * Coverts ticks to time in seconds
365
+ * @param ticks {number}
366
+ * @returns {number}
367
+ * @private
368
+ */
369
+ _ticksToSeconds(ticks)
370
+ {
371
+ if (ticks <= 0) {
372
+ return 0;
373
+ }
374
+
375
+ // find the last tempo change that has occured
376
+ let tempo = this.tempoChanges.find(v => v.ticks < ticks);
377
+
378
+ let timeSinceLastTempo = ticks - tempo.ticks;
379
+ return this._ticksToSeconds(ticks - timeSinceLastTempo) + (timeSinceLastTempo * 60) / (tempo.tempo * this.timeDivision);
380
+ }
381
+ }
@@ -0,0 +1,231 @@
1
+ import {ShiftableByteArray} from "../utils/shiftable_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 {ShiftableByteArray}
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
+ this.messageData = data;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Gets the status byte's channel
26
+ * @param statusByte
27
+ * @returns {number} channel is -1 for system messages -2 for meta and -3 for sysex
28
+ */
29
+ export function getChannel(statusByte) {
30
+ const eventType = statusByte & 0xF0;
31
+ const channel = statusByte & 0x0F;
32
+
33
+ let resultChannel = channel;
34
+
35
+ switch (eventType) {
36
+ // midi (and meta and sysex headers)
37
+ case 0x80:
38
+ case 0x90:
39
+ case 0xA0:
40
+ case 0xB0:
41
+ case 0xC0:
42
+ case 0xD0:
43
+ case 0xE0:
44
+ break;
45
+
46
+ case 0xF0:
47
+ switch (channel) {
48
+ case 0x0:
49
+ resultChannel = -3;
50
+ break;
51
+
52
+ case 0x1:
53
+ case 0x2:
54
+ case 0x3:
55
+ case 0x4:
56
+ case 0x5:
57
+ case 0x6:
58
+ case 0x7:
59
+ case 0x8:
60
+ case 0x9:
61
+ case 0xA:
62
+ case 0xB:
63
+ case 0xC:
64
+ case 0xD:
65
+ case 0xE:
66
+ resultChannel = -1;
67
+ break;
68
+
69
+ case 0xF:
70
+ resultChannel = -2;
71
+ break;
72
+ }
73
+ break;
74
+
75
+ default:
76
+ resultChannel = -1;
77
+ }
78
+
79
+ return resultChannel;
80
+ }
81
+
82
+ // all the midi statuses dictionary
83
+ export const messageTypes = {
84
+ noteOff: 0x80,
85
+ noteOn: 0x90,
86
+ noteAftertouch: 0xA0,
87
+ controllerChange: 0xB0,
88
+ programChange: 0xC0,
89
+ channelAftertouch: 0xD0,
90
+ pitchBend: 0xE0,
91
+ systemExclusive: 0xF0,
92
+ timecode: 0xF1,
93
+ songPosition: 0xF2,
94
+ songSelect: 0xF3,
95
+ tuneRequest: 0xF6,
96
+ clock: 0xF8,
97
+ start: 0xFA,
98
+ continue: 0xFB,
99
+ stop: 0xFC,
100
+ activeSensing: 0xFE,
101
+ reset: 0xFF,
102
+ sequenceNumber: 0x00,
103
+ text: 0x01,
104
+ copyright: 0x02,
105
+ trackName: 0x03,
106
+ instrumentName: 0x04,
107
+ lyric: 0x05,
108
+ marker: 0x06,
109
+ cuePoint: 0x07,
110
+ midiChannelPrefix: 0x20,
111
+ midiPort: 0x21,
112
+ endOfTrack: 0x2F,
113
+ setTempo: 0x51,
114
+ smpteOffset: 0x54,
115
+ timeSignature: 0x58,
116
+ keySignature: 0x59,
117
+ sequenceSpecific: 0x7F
118
+ };
119
+
120
+
121
+ /**
122
+ * Gets the event's status and channel from the status byte
123
+ * @param statusByte {number} the status byte
124
+ * @returns {{channel: number, status: number}} channel will be -1 for sysex and meta
125
+ */
126
+ export function getEvent(statusByte) {
127
+ const status = statusByte & 0xF0;
128
+ const channel = statusByte & 0x0F;
129
+
130
+ let eventChannel = -1;
131
+ let eventStatus = statusByte;
132
+
133
+ if (status >= 0x80 && status <= 0xE0) {
134
+ eventChannel = channel;
135
+ eventStatus = status;
136
+ }
137
+
138
+ return {
139
+ status: eventStatus,
140
+ channel: eventChannel
141
+ };
142
+ }
143
+
144
+
145
+ /**
146
+ * @type {{timbreHarmonicContent: number, omniModeOn: number, polyModeOn: number, localControlOnOff: number, NRPNLsb: number, allNotesOff: number, footController: number, effects2Depth: number, lsbForControl7MainVolume: number, expressionController: number, monoModeOn: number, balance: number, effectControl1: number, effectControl2: number, modulationWheel: number, lsbForControl1ModulationWheel: number, allSoundOff: number, pan: number, effects1Depth: number, effects3Depth: number, attackTime: number, dataEntryMsb: number, portamentoControl: number, sostenutoPedal: number, lsbForControl5PortamentoTime: number, RPNLsb: number, bankSelect: number, portamentoTime: number, mainVolume: number, hold2Pedal: number, releaseTime: number, dataDecrement: number, NRPNMsb: number, legatoFootswitch: number, sustainPedal: number, portamentoOnOff: number, lsbForControl0BankSelect: number, lsbForControl13EffectControl2: number, effects5Depth: number, generalPurposeController2: number, lsbForControl6DataEntry: number, resetAllControllers: number, generalPurposeController3: number, generalPurposeController4: number, generalPurposeController5: number, softPedal: number, generalPurposeController6: number, lsbForControl4FootController: number, lsbForControl12EffectControl1: number, generalPurposeController7: number, generalPurposeController8: number, effects4Depth: number, lsbForControl8Balance: number, soundController9: number, soundVariation: number, soundController8: number, soundController7: number, soundController6: number, soundController10: number, dataIncrement: number, generalPurposeController1: number, lsbForControl2BreathController: number, lsbForControl11ExpressionController: number, brightness: number, lsbForControl10Pan: number, RPNMsb: number, breathController: number, omniModeOff: number}}
147
+ */
148
+ export const midiControllers = {
149
+ bankSelect: 0,
150
+ modulationWheel: 1,
151
+ breathController: 2,
152
+ footController: 4,
153
+ portamentoTime: 5,
154
+ dataEntryMsb: 6,
155
+ mainVolume: 7,
156
+ balance: 8,
157
+ pan: 10,
158
+ expressionController: 11,
159
+ effectControl1: 12,
160
+ effectControl2: 13,
161
+ generalPurposeController1: 16,
162
+ generalPurposeController2: 17,
163
+ generalPurposeController3: 18,
164
+ generalPurposeController4: 19,
165
+ lsbForControl0BankSelect: 32,
166
+ lsbForControl1ModulationWheel: 33,
167
+ lsbForControl2BreathController: 34,
168
+ lsbForControl4FootController: 36,
169
+ lsbForControl5PortamentoTime: 37,
170
+ lsbForControl6DataEntry: 38,
171
+ lsbForControl7MainVolume: 39,
172
+ lsbForControl8Balance: 40,
173
+ lsbForControl10Pan: 42,
174
+ lsbForControl11ExpressionController: 43,
175
+ lsbForControl12EffectControl1: 44,
176
+ lsbForControl13EffectControl2: 45,
177
+ sustainPedal: 64,
178
+ portamentoOnOff: 65,
179
+ sostenutoPedal: 66,
180
+ softPedal: 67,
181
+ legatoFootswitch: 68,
182
+ hold2Pedal: 69,
183
+ soundVariation: 70,
184
+ timbreHarmonicContent: 71,
185
+ releaseTime: 72,
186
+ attackTime: 73,
187
+ brightness: 74,
188
+ soundController6: 75,
189
+ soundController7: 76,
190
+ soundController8: 77,
191
+ soundController9: 78,
192
+ soundController10: 79,
193
+ generalPurposeController5: 80,
194
+ generalPurposeController6: 81,
195
+ generalPurposeController7: 82,
196
+ generalPurposeController8: 83,
197
+ portamentoControl: 84,
198
+ effects1Depth: 91,
199
+ effects2Depth: 92,
200
+ effects3Depth: 93,
201
+ effects4Depth: 94,
202
+ effects5Depth: 95,
203
+ dataIncrement: 96,
204
+ dataDecrement: 97,
205
+ NRPNLsb: 98,
206
+ NRPNMsb: 99,
207
+ RPNLsb: 100,
208
+ RPNMsb: 101,
209
+ allSoundOff: 120,
210
+ resetAllControllers: 121,
211
+ localControlOnOff: 122,
212
+ allNotesOff: 123,
213
+ omniModeOff: 124,
214
+ omniModeOn: 125,
215
+ monoModeOn: 126,
216
+ polyModeOn: 127
217
+ };
218
+
219
+
220
+ /**
221
+ * @type {{"11": number, "12": number, "13": number, "14": number, "8": number, "9": number, "10": number}}
222
+ */
223
+ export const dataBytesAmount = {
224
+ 0x8: 2, // note off
225
+ 0x9: 2, // note on
226
+ 0xA: 2, // note at
227
+ 0xB: 2, // cc change
228
+ 0xC: 1, // pg change
229
+ 0xD: 1, // channel aftertouch
230
+ 0xE: 2 // pitch wheel
231
+ };