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.
- package/.idea/modules.xml +8 -0
- package/.idea/spessasynth_lib.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/copy_version.sh +38 -0
- package/index.js +73 -0
- package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
- package/package/@types/index.d.ts +34 -0
- package/package/@types/midi_handler/midi_handler.d.ts +39 -0
- package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
- package/package/@types/midi_parser/midi_data.d.ts +95 -0
- package/package/@types/midi_parser/midi_editor.d.ts +45 -0
- package/package/@types/midi_parser/midi_loader.d.ts +100 -0
- package/package/@types/midi_parser/midi_message.d.ts +154 -0
- package/package/@types/midi_parser/midi_writer.d.ts +6 -0
- package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
- package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
- package/package/@types/sequencer/sequencer.d.ts +180 -0
- package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
- package/package/@types/soundfont/read/generators.d.ts +98 -0
- package/package/@types/soundfont/read/instruments.d.ts +50 -0
- package/package/@types/soundfont/read/modulators.d.ts +73 -0
- package/package/@types/soundfont/read/presets.d.ts +87 -0
- package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
- package/package/@types/soundfont/read/samples.d.ts +134 -0
- package/package/@types/soundfont/read/zones.d.ts +141 -0
- package/package/@types/soundfont/soundfont.d.ts +76 -0
- package/package/@types/soundfont/write/ibag.d.ts +6 -0
- package/package/@types/soundfont/write/igen.d.ts +6 -0
- package/package/@types/soundfont/write/imod.d.ts +6 -0
- package/package/@types/soundfont/write/inst.d.ts +6 -0
- package/package/@types/soundfont/write/pbag.d.ts +6 -0
- package/package/@types/soundfont/write/pgen.d.ts +6 -0
- package/package/@types/soundfont/write/phdr.d.ts +6 -0
- package/package/@types/soundfont/write/pmod.d.ts +6 -0
- package/package/@types/soundfont/write/sdta.d.ts +11 -0
- package/package/@types/soundfont/write/shdr.d.ts +8 -0
- package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
- package/package/@types/soundfont/write/write.d.ts +21 -0
- package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
- package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
- package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
- package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
- package/package/@types/synthetizer/synthetizer.d.ts +294 -0
- package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
- package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
- package/package/@types/synthetizer/worklet_url.d.ts +5 -0
- package/package/@types/utils/buffer_to_wav.d.ts +8 -0
- package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
- package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
- package/package/@types/utils/byte_functions/string.d.ts +22 -0
- package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
- package/package/@types/utils/indexed_array.d.ts +21 -0
- package/package/@types/utils/loggin.d.ts +26 -0
- package/package/@types/utils/other.d.ts +32 -0
- package/package/LICENSE +26 -0
- package/package/README.md +84 -0
- package/package/externals/NOTICE +9 -0
- package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
- package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
- package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/package/externals/stbvorbis_sync/LICENSE +202 -0
- package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/package/index.js +73 -0
- package/package/midi_handler/README.md +3 -0
- package/package/midi_handler/midi_handler.js +118 -0
- package/package/midi_handler/web_midi_link.js +41 -0
- package/package/midi_parser/README.md +3 -0
- package/package/midi_parser/midi_data.js +121 -0
- package/package/midi_parser/midi_editor.js +557 -0
- package/package/midi_parser/midi_loader.js +502 -0
- package/package/midi_parser/midi_message.js +234 -0
- package/package/midi_parser/midi_writer.js +95 -0
- package/package/midi_parser/rmidi_writer.js +271 -0
- package/package/midi_parser/used_keys_loaded.js +172 -0
- package/package/package.json +43 -0
- package/package/sequencer/README.md +23 -0
- package/package/sequencer/sequencer.js +439 -0
- package/package/sequencer/worklet_sequencer/events.js +92 -0
- package/package/sequencer/worklet_sequencer/play.js +309 -0
- package/package/sequencer/worklet_sequencer/process_event.js +167 -0
- package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
- package/package/sequencer/worklet_sequencer/song_control.js +193 -0
- package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
- package/package/soundfont/README.md +8 -0
- package/package/soundfont/read/generators.js +212 -0
- package/package/soundfont/read/instruments.js +125 -0
- package/package/soundfont/read/modulators.js +249 -0
- package/package/soundfont/read/presets.js +300 -0
- package/package/soundfont/read/riff_chunk.js +81 -0
- package/package/soundfont/read/samples.js +398 -0
- package/package/soundfont/read/zones.js +310 -0
- package/package/soundfont/soundfont.js +357 -0
- package/package/soundfont/write/ibag.js +39 -0
- package/package/soundfont/write/igen.js +75 -0
- package/package/soundfont/write/imod.js +46 -0
- package/package/soundfont/write/inst.js +34 -0
- package/package/soundfont/write/pbag.js +39 -0
- package/package/soundfont/write/pgen.js +77 -0
- package/package/soundfont/write/phdr.js +42 -0
- package/package/soundfont/write/pmod.js +46 -0
- package/package/soundfont/write/sdta.js +72 -0
- package/package/soundfont/write/shdr.js +54 -0
- package/package/soundfont/write/soundfont_trimmer.js +169 -0
- package/package/soundfont/write/write.js +180 -0
- package/package/synthetizer/README.md +6 -0
- package/package/synthetizer/audio_effects/effects_config.js +21 -0
- package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
- package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/package/synthetizer/audio_effects/reverb.js +24 -0
- package/package/synthetizer/synth_event_handler.js +156 -0
- package/package/synthetizer/synthetizer.js +766 -0
- package/package/synthetizer/worklet_processor.min.js +13 -0
- package/package/synthetizer/worklet_system/README.md +6 -0
- package/package/synthetizer/worklet_system/main_processor.js +363 -0
- package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
- package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
- package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
- package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
- package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
- package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
- package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
- package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
- package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
- package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
- package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
- package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
- package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
- package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
- package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
- package/package/synthetizer/worklet_url.js +5 -0
- package/package/utils/README.md +4 -0
- package/package/utils/buffer_to_wav.js +101 -0
- package/package/utils/byte_functions/big_endian.js +28 -0
- package/package/utils/byte_functions/little_endian.js +74 -0
- package/package/utils/byte_functions/string.js +97 -0
- package/package/utils/byte_functions/variable_length_quantity.js +37 -0
- package/package/utils/encode_vorbis.js +30 -0
- package/package/utils/indexed_array.js +41 -0
- package/package/utils/loggin.js +79 -0
- package/package/utils/other.js +54 -0
- 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
|
+
}
|