spessasynth_lib 3.16.5 → 3.20.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.
- package/@types/index.d.ts +5 -4
- package/@types/midi_parser/basic_midi.d.ts +125 -0
- package/@types/midi_parser/midi_builder.d.ts +69 -0
- package/@types/midi_parser/midi_data.d.ts +2 -2
- package/@types/midi_parser/midi_editor.d.ts +4 -4
- package/@types/midi_parser/midi_loader.d.ts +3 -100
- package/@types/midi_parser/midi_writer.d.ts +2 -2
- package/@types/midi_parser/rmidi_writer.d.ts +3 -3
- package/@types/midi_parser/used_keys_loaded.d.ts +2 -2
- package/@types/sequencer/sequencer.d.ts +1 -1
- package/@types/soundfont/basic_soundfont/basic_sample.d.ts +2 -2
- package/@types/soundfont/basic_soundfont/basic_zone.d.ts +12 -12
- package/@types/soundfont/basic_soundfont/basic_zones.d.ts +4 -0
- package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +6 -0
- package/@types/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.d.ts +2 -2
- package/@types/soundfont/dls/articulator_converter.d.ts +10 -0
- package/@types/soundfont/dls/dls_destinations.d.ts +29 -0
- package/@types/soundfont/dls/dls_preset.d.ts +13 -0
- package/@types/soundfont/dls/dls_sample.d.ts +18 -0
- package/@types/soundfont/dls/dls_soundfont.d.ts +31 -0
- package/@types/soundfont/dls/dls_sources.d.ts +22 -0
- package/@types/soundfont/dls/dls_zone.d.ts +22 -0
- package/@types/soundfont/dls/read_articulation.d.ts +12 -0
- package/@types/soundfont/dls/read_instrument.d.ts +5 -0
- package/@types/soundfont/dls/read_instrument_list.d.ts +5 -0
- package/@types/soundfont/dls/read_lart.d.ts +7 -0
- package/@types/soundfont/dls/read_region.d.ts +7 -0
- package/@types/soundfont/dls/read_samples.d.ts +5 -0
- package/@types/soundfont/load_soundfont.d.ts +6 -0
- package/@types/soundfont/read_sf2/generators.d.ts +18 -5
- package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
- package/@types/soundfont/soundfont.d.ts +2 -1
- package/@types/synthetizer/synthetizer.d.ts +2 -2
- package/@types/utils/byte_functions/little_endian.d.ts +1 -1
- package/README.md +27 -15
- package/index.js +6 -4
- package/midi_parser/basic_midi.js +146 -0
- package/midi_parser/midi_builder.js +281 -0
- package/midi_parser/midi_data.js +1 -1
- package/midi_parser/midi_editor.js +2 -2
- package/midi_parser/midi_loader.js +38 -56
- package/midi_parser/midi_writer.js +1 -1
- package/midi_parser/rmidi_writer.js +2 -2
- package/midi_parser/used_keys_loaded.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +3 -3
- package/sequencer/worklet_sequencer/worklet_sequencer.js +1 -1
- package/soundfont/README.md +6 -2
- package/soundfont/basic_soundfont/basic_sample.js +3 -3
- package/soundfont/basic_soundfont/basic_zone.js +28 -28
- package/soundfont/basic_soundfont/basic_zones.js +15 -19
- package/soundfont/basic_soundfont/riff_chunk.js +20 -4
- package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +1 -1
- package/soundfont/dls/articulator_converter.js +311 -0
- package/soundfont/dls/dls_destinations.js +38 -0
- package/soundfont/dls/dls_preset.js +32 -0
- package/soundfont/dls/dls_sample.js +58 -0
- package/soundfont/dls/dls_soundfont.js +150 -0
- package/soundfont/dls/dls_sources.js +26 -0
- package/soundfont/dls/dls_zone.js +75 -0
- package/soundfont/dls/read_articulation.js +327 -0
- package/soundfont/dls/read_instrument.js +100 -0
- package/soundfont/dls/read_instrument_list.js +17 -0
- package/soundfont/dls/read_lart.js +35 -0
- package/soundfont/dls/read_region.js +129 -0
- package/soundfont/dls/read_samples.js +174 -0
- package/soundfont/load_soundfont.js +21 -0
- package/soundfont/read_sf2/generators.js +41 -6
- package/soundfont/read_sf2/instruments.js +2 -2
- package/soundfont/read_sf2/modulators.js +8 -8
- package/soundfont/read_sf2/presets.js +7 -7
- package/soundfont/read_sf2/samples.js +8 -8
- package/soundfont/read_sf2/zones.js +5 -5
- package/soundfont/soundfont.js +8 -3
- package/synthetizer/synthetizer.js +1 -1
- package/synthetizer/worklet_processor.min.js +10 -7
- package/synthetizer/worklet_system/main_processor.js +1 -2
- package/synthetizer/worklet_system/worklet_methods/program_control.js +6 -3
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +5 -5
- package/utils/buffer_to_wav.js +5 -26
- package/utils/byte_functions/little_endian.js +1 -1
- /package/@types/{midi_handler → external_midi}/midi_handler.d.ts +0 -0
- /package/@types/{midi_handler → external_midi}/web_midi_link.d.ts +0 -0
- /package/{midi_handler → external_midi}/README.md +0 -0
- /package/{midi_handler → external_midi}/midi_handler.js +0 -0
- /package/{midi_handler → external_midi}/web_midi_link.js +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { BasicMIDI } from './basic_midi.js'
|
|
2
|
+
import { messageTypes, MidiMessage } from './midi_message.js'
|
|
3
|
+
import { IndexedByteArray } from '../utils/indexed_array.js'
|
|
4
|
+
import { readBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js'
|
|
5
|
+
import { SpessaSynthWarn } from '../utils/loggin.js'
|
|
6
|
+
|
|
7
|
+
export class MIDIBuilder extends BasicMIDI
|
|
8
|
+
{
|
|
9
|
+
/**
|
|
10
|
+
* @param name {string} The MIDI's name
|
|
11
|
+
* @param timeDivision {number} the file's time division
|
|
12
|
+
* @param initialTempo {number} the file's initial tempo
|
|
13
|
+
*/
|
|
14
|
+
constructor(name, timeDivision = 480, initialTempo = 120)
|
|
15
|
+
{
|
|
16
|
+
super();
|
|
17
|
+
this.timeDivision = timeDivision;
|
|
18
|
+
this.midiName = name;
|
|
19
|
+
this.encoder = new TextEncoder();
|
|
20
|
+
this.rawMidiName = this.encoder.encode(name);
|
|
21
|
+
|
|
22
|
+
// create the first track with the file name
|
|
23
|
+
this.addNewTrack(name);
|
|
24
|
+
this.addSetTempo(0, initialTempo);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Updates all internal values
|
|
29
|
+
*/
|
|
30
|
+
flush()
|
|
31
|
+
{
|
|
32
|
+
|
|
33
|
+
// find first note on
|
|
34
|
+
const firstNoteOns = [];
|
|
35
|
+
for(const t of this.tracks)
|
|
36
|
+
{
|
|
37
|
+
// sost the track by ticks
|
|
38
|
+
t.sort((e1, e2) => e1.ticks - e2.ticks);
|
|
39
|
+
const firstNoteOn = t.find(e => (e.messageStatusByte & 0xF0) === messageTypes.noteOn);
|
|
40
|
+
if(firstNoteOn)
|
|
41
|
+
{
|
|
42
|
+
firstNoteOns.push(firstNoteOn.ticks);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
this.firstNoteOn = Math.min(...firstNoteOns);
|
|
46
|
+
|
|
47
|
+
// find tempo changes
|
|
48
|
+
// and used channels on tracks
|
|
49
|
+
// and midi ports
|
|
50
|
+
// and last voice event tick
|
|
51
|
+
// and loop
|
|
52
|
+
this.lastVoiceEventTick = 0
|
|
53
|
+
this.tempoChanges = [{ticks: 0, tempo: 120}];
|
|
54
|
+
this.midiPorts = [];
|
|
55
|
+
this.midiPortChannelOffsets = [];
|
|
56
|
+
let portOffset = 0;
|
|
57
|
+
/**
|
|
58
|
+
* @type {Set<number>[]}
|
|
59
|
+
*/
|
|
60
|
+
this.usedChannelsOnTrack = this.tracks.map(() => new Set());
|
|
61
|
+
this.tracks.forEach((t, trackNum) => {
|
|
62
|
+
this.midiPorts.push(-1);
|
|
63
|
+
t.forEach(e => {
|
|
64
|
+
// last voice event tick
|
|
65
|
+
if(e.messageStatusByte >= 0x80 && e.messageStatusByte < 0xF0)
|
|
66
|
+
{
|
|
67
|
+
if(e.ticks > this.lastVoiceEventTick)
|
|
68
|
+
{
|
|
69
|
+
this.lastVoiceEventTick = e.ticks;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// tempo, used channels, port
|
|
74
|
+
if(e.messageStatusByte === messageTypes.setTempo)
|
|
75
|
+
{
|
|
76
|
+
this.tempoChanges.push({
|
|
77
|
+
ticks: e.ticks,
|
|
78
|
+
tempo: 60000000 / readBytesAsUintBigEndian(e.messageData, 3)
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
if((e.messageStatusByte & 0xF0) === messageTypes.noteOn)
|
|
83
|
+
{
|
|
84
|
+
this.usedChannelsOnTrack[trackNum].add(e.messageData[0]);
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
if(e.messageStatusByte === messageTypes.midiPort)
|
|
88
|
+
{
|
|
89
|
+
const port = e.messageData[0];
|
|
90
|
+
this.midiPorts[trackNum] = port;
|
|
91
|
+
if(this.midiPortChannelOffsets[port] === undefined)
|
|
92
|
+
{
|
|
93
|
+
this.midiPortChannelOffsets[port] = portOffset;
|
|
94
|
+
portOffset += 16;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.loop = {start: this.firstNoteOn, end: this.lastVoiceEventTick};
|
|
101
|
+
|
|
102
|
+
// reverse tempo and compute duration
|
|
103
|
+
this.tempoChanges.reverse();
|
|
104
|
+
this.duration = this._ticksToSeconds(this.lastVoiceEventTick);
|
|
105
|
+
|
|
106
|
+
// fix midi ports:
|
|
107
|
+
// midi tracks without ports will have a value of -1
|
|
108
|
+
// if all ports have a value of -1, set it to 0, otherwise take the first midi port and replace all -1 with it
|
|
109
|
+
// why do this? some midis (for some reason) specify all channels to port 1 or else, but leave the conductor track with no port pref.
|
|
110
|
+
// this spessasynth to reserve the first 16 channels for the conductor track (which doesn't play anything) and use additional 16 for the actual ports.
|
|
111
|
+
let defaultP = 0;
|
|
112
|
+
for(let port of this.midiPorts)
|
|
113
|
+
{
|
|
114
|
+
if(port !== -1)
|
|
115
|
+
{
|
|
116
|
+
defaultP = port;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.midiPorts = this.midiPorts.map(port => port === -1 ? defaultP : port);
|
|
121
|
+
// add dummy port if empty
|
|
122
|
+
if(this.midiPortChannelOffsets.length === 0)
|
|
123
|
+
{
|
|
124
|
+
this.midiPortChannelOffsets = [0];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Adds a new "set tempo" message
|
|
130
|
+
* @param ticks {number} the tick number of the event
|
|
131
|
+
* @param tempo {number} the tempo in beats per minute (BPM)
|
|
132
|
+
*/
|
|
133
|
+
addSetTempo(ticks, tempo)
|
|
134
|
+
{
|
|
135
|
+
const array = new IndexedByteArray(3);
|
|
136
|
+
|
|
137
|
+
tempo = 60000000 / tempo;
|
|
138
|
+
|
|
139
|
+
// Extract each byte in big-endian order
|
|
140
|
+
array[0] = (tempo >> 16) & 0xFF;
|
|
141
|
+
array[1] = (tempo >> 8) & 0xFF;
|
|
142
|
+
array[2] = tempo & 0xFF;
|
|
143
|
+
|
|
144
|
+
this.addEvent(ticks, 0, messageTypes.setTempo, array);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Adds a new MIDI track
|
|
149
|
+
* @param name {string} the new track's name
|
|
150
|
+
* @param port {number} the new track's port
|
|
151
|
+
*/
|
|
152
|
+
addNewTrack(name, port = 0)
|
|
153
|
+
{
|
|
154
|
+
this.tracksAmount++;
|
|
155
|
+
if(this.tracksAmount > 1)
|
|
156
|
+
{
|
|
157
|
+
this.format = 1;
|
|
158
|
+
}
|
|
159
|
+
this.tracks.push([]);
|
|
160
|
+
this.tracks[this.tracksAmount - 1].push(
|
|
161
|
+
new MidiMessage(0, messageTypes.endOfTrack, new IndexedByteArray(0))
|
|
162
|
+
);
|
|
163
|
+
this.addEvent(0, this.tracksAmount - 1, messageTypes.trackName, this.encoder.encode(name));
|
|
164
|
+
this.addEvent(0, this.tracksAmount - 1, messageTypes.midiPort, [port]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Adds a new MIDI Event
|
|
169
|
+
* @param ticks {number} the tick time of the event
|
|
170
|
+
* @param track {number} the track number to use
|
|
171
|
+
* @param event {number} the MIDI event number
|
|
172
|
+
* @param eventData {Uint8Array|Iterable<number>} the raw event data
|
|
173
|
+
*/
|
|
174
|
+
addEvent(ticks, track, event, eventData)
|
|
175
|
+
{
|
|
176
|
+
if(!this.tracks[track])
|
|
177
|
+
{
|
|
178
|
+
throw new Error(`Track ${track} does not exist. Add it via addTrack method.`);
|
|
179
|
+
}
|
|
180
|
+
if(event === messageTypes.endOfTrack)
|
|
181
|
+
{
|
|
182
|
+
SpessaSynthWarn("The EndOfTrack is added automatically. Ignoring!");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// remove end of track
|
|
186
|
+
this.tracks[track].pop();
|
|
187
|
+
this.tracks[track].push(new MidiMessage(
|
|
188
|
+
ticks,
|
|
189
|
+
event,
|
|
190
|
+
new IndexedByteArray(eventData)
|
|
191
|
+
));
|
|
192
|
+
// add end of track
|
|
193
|
+
this.tracks[track].push(new MidiMessage(
|
|
194
|
+
ticks,
|
|
195
|
+
messageTypes.endOfTrack,
|
|
196
|
+
new IndexedByteArray(0)
|
|
197
|
+
));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Adds a new Note On event
|
|
202
|
+
* @param ticks {number} the tick time of the event
|
|
203
|
+
* @param track {number} the track number to use
|
|
204
|
+
* @param channel {number} the channel to use
|
|
205
|
+
* @param midiNote {number} the midi note of the keypress
|
|
206
|
+
* @param velocity {number} the velocity of the keypress
|
|
207
|
+
*/
|
|
208
|
+
addNoteOn(ticks, track, channel, midiNote, velocity)
|
|
209
|
+
{
|
|
210
|
+
channel %= 16;
|
|
211
|
+
midiNote %= 128;
|
|
212
|
+
velocity %= 128;
|
|
213
|
+
this.addEvent(
|
|
214
|
+
ticks,
|
|
215
|
+
track,
|
|
216
|
+
messageTypes.noteOn | channel,
|
|
217
|
+
[midiNote, velocity]
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Adds a new Note Off event
|
|
223
|
+
* @param ticks {number} the tick time of the event
|
|
224
|
+
* @param track {number} the track number to use
|
|
225
|
+
* @param channel {number} the channel to use
|
|
226
|
+
* @param midiNote {number} the midi note of the key release
|
|
227
|
+
*/
|
|
228
|
+
addNoteOff(ticks, track, channel, midiNote)
|
|
229
|
+
{
|
|
230
|
+
channel %= 16;
|
|
231
|
+
midiNote %= 128;
|
|
232
|
+
this.addEvent(
|
|
233
|
+
ticks,
|
|
234
|
+
track,
|
|
235
|
+
messageTypes.noteOff | channel,
|
|
236
|
+
[midiNote, 64]
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Adds a new Controller Change event
|
|
242
|
+
* @param ticks {number} the tick time of the event
|
|
243
|
+
* @param track {number} the track number to use
|
|
244
|
+
* @param channel {number} the channel to use
|
|
245
|
+
* @param controllerNumber {number} the MIDI CC to use
|
|
246
|
+
* @param controllerValue {number} the new CC value
|
|
247
|
+
*/
|
|
248
|
+
addControllerChange(ticks, track, channel, controllerNumber, controllerValue)
|
|
249
|
+
{
|
|
250
|
+
channel %= 16;
|
|
251
|
+
controllerNumber %= 128;
|
|
252
|
+
controllerValue %= 128;
|
|
253
|
+
this.addEvent(
|
|
254
|
+
ticks,
|
|
255
|
+
track,
|
|
256
|
+
messageTypes.controllerChange | channel,
|
|
257
|
+
[controllerNumber, controllerValue]
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Adds a new Pitch Wheel event
|
|
263
|
+
* @param ticks {number} the tick time of the event
|
|
264
|
+
* @param track {number} the track to use
|
|
265
|
+
* @param channel {number} the channel to use
|
|
266
|
+
* @param MSB {number} SECOND byte of the MIDI pitchWheel message
|
|
267
|
+
* @param LSB {number} FIRST byte of the MIDI pitchWheel message
|
|
268
|
+
*/
|
|
269
|
+
addPitchWheel(ticks, track, channel, MSB, LSB)
|
|
270
|
+
{
|
|
271
|
+
channel %= 16;
|
|
272
|
+
MSB %= 128;
|
|
273
|
+
LSB %= 128;
|
|
274
|
+
this.addEvent(
|
|
275
|
+
ticks,
|
|
276
|
+
track,
|
|
277
|
+
messageTypes.pitchBend | channel,
|
|
278
|
+
[LSB, MSB]
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
package/midi_parser/midi_data.js
CHANGED
|
@@ -75,7 +75,7 @@ function getDrumChange(channel, ticks)
|
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* Allows easy editing of the file
|
|
78
|
-
* @param midi {
|
|
78
|
+
* @param midi {BasicMIDI}
|
|
79
79
|
* @param desiredProgramChanges {{
|
|
80
80
|
* channel: number,
|
|
81
81
|
* program: number,
|
|
@@ -520,7 +520,7 @@ export function modifyMIDI(
|
|
|
520
520
|
|
|
521
521
|
/**
|
|
522
522
|
* Modifies the sequence according to the locked presets and controllers in the given snapshot
|
|
523
|
-
* @param midi {
|
|
523
|
+
* @param midi {BasicMIDI}
|
|
524
524
|
* @param snapshot {SynthesizerSnapshot}
|
|
525
525
|
*/
|
|
526
526
|
export function applySnapshotToMIDI(midi, snapshot)
|
|
@@ -1,63 +1,40 @@
|
|
|
1
1
|
import { dataBytesAmount, getChannel, messageTypes, MidiMessage } from './midi_message.js'
|
|
2
2
|
import { IndexedByteArray } from '../utils/indexed_array.js'
|
|
3
3
|
import { arrayToHexString, consoleColors, formatTitle } from '../utils/other.js'
|
|
4
|
-
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from '../utils/loggin.js'
|
|
4
|
+
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
|
|
5
5
|
import { readRIFFChunk } from '../soundfont/basic_soundfont/riff_chunk.js'
|
|
6
6
|
import { readVariableLengthQuantity } from '../utils/byte_functions/variable_length_quantity.js'
|
|
7
7
|
import { readBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js'
|
|
8
8
|
import { readBytesAsString } from '../utils/byte_functions/string.js'
|
|
9
|
-
import {
|
|
9
|
+
import { readLittleEndian } from '../utils/byte_functions/little_endian.js'
|
|
10
10
|
import { RMIDINFOChunks } from './rmidi_writer.js'
|
|
11
|
+
import { BasicMIDI } from './basic_midi.js'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* midi_loader.js
|
|
14
15
|
* purpose: parses a midi file for the seqyencer, including things like marker or CC 2/4 loop detection, copyright detection etc.
|
|
15
16
|
*/
|
|
16
|
-
class MIDI
|
|
17
|
+
class MIDI extends BasicMIDI
|
|
18
|
+
{
|
|
17
19
|
/**
|
|
18
20
|
* Parses a given midi file
|
|
19
21
|
* @param arrayBuffer {ArrayBuffer}
|
|
20
22
|
* @param fileName {string} optional, replaces the decoded title if empty
|
|
21
23
|
*/
|
|
22
|
-
constructor(arrayBuffer, fileName="")
|
|
24
|
+
constructor(arrayBuffer, fileName="")
|
|
25
|
+
{
|
|
26
|
+
super();
|
|
23
27
|
SpessaSynthGroupCollapsed(`%cParsing MIDI File...`, consoleColors.info);
|
|
24
28
|
const binaryData = new IndexedByteArray(arrayBuffer);
|
|
25
29
|
let fileByteArray;
|
|
26
30
|
|
|
27
31
|
// check for rmid
|
|
28
|
-
/**
|
|
29
|
-
* If the RMI file has an embedded sf2 in it, it will appeear here, otherwise undefined
|
|
30
|
-
* @type {ArrayBuffer}
|
|
31
|
-
*/
|
|
32
|
-
this.embeddedSoundFont = undefined;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The RMID Info data if RMID, otherwise undefined
|
|
36
|
-
* @type {Object<string, IndexedByteArray>}
|
|
37
|
-
*/
|
|
38
|
-
this.RMIDInfo = undefined;
|
|
39
|
-
/**
|
|
40
|
-
* The bank offset for RMIDI
|
|
41
|
-
* @type {number}
|
|
42
|
-
*/
|
|
43
|
-
this.bankOffset = 0;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Contains the copyright strings
|
|
47
|
-
* @type {string}
|
|
48
|
-
*/
|
|
49
|
-
this.copyright = "";
|
|
50
32
|
let copyrightDetected = false;
|
|
51
33
|
|
|
52
|
-
/**
|
|
53
|
-
* The MIDI name
|
|
54
|
-
* @type {string}
|
|
55
|
-
*/
|
|
56
|
-
this.midiName = "";
|
|
57
|
-
|
|
58
|
-
this.rawMidiName = new Uint8Array(0);
|
|
59
34
|
let nameDetected = false;
|
|
60
35
|
|
|
36
|
+
let DLSRMID = false;
|
|
37
|
+
|
|
61
38
|
const initialString = readBytesAsString(binaryData, 4);
|
|
62
39
|
binaryData.currentIndex -= 4;
|
|
63
40
|
if(initialString === "RIFF")
|
|
@@ -87,12 +64,21 @@ class MIDI{
|
|
|
87
64
|
const currentChunk = readRIFFChunk(binaryData, true);
|
|
88
65
|
if(currentChunk.header === "RIFF")
|
|
89
66
|
{
|
|
90
|
-
const type = readBytesAsString(currentChunk.chunkData, 4);
|
|
91
|
-
if(type === "sfbk" || type === "sfpk")
|
|
67
|
+
const type = readBytesAsString(currentChunk.chunkData, 4).toLowerCase();
|
|
68
|
+
if(type === "sfbk" || type === "sfpk" || type === "dls ")
|
|
92
69
|
{
|
|
93
70
|
SpessaSynthInfo("%cFound embedded soundfont!", consoleColors.recognized);
|
|
94
71
|
this.embeddedSoundFont = binaryData.slice(startIndex, startIndex + currentChunk.size).buffer;
|
|
95
72
|
}
|
|
73
|
+
else
|
|
74
|
+
{
|
|
75
|
+
SpessaSynthWarn(`Unknown RIFF chunk: "${type}"`);
|
|
76
|
+
}
|
|
77
|
+
if(type === "dls ")
|
|
78
|
+
{
|
|
79
|
+
// assume bank offset of 0 by default. If we find any bank selects, then the offset is 1.
|
|
80
|
+
DLSRMID = true;
|
|
81
|
+
}
|
|
96
82
|
}
|
|
97
83
|
else if(currentChunk.header === "LIST")
|
|
98
84
|
{
|
|
@@ -129,11 +115,18 @@ class MIDI{
|
|
|
129
115
|
this.bankOffset = 1; // defaults to 1
|
|
130
116
|
if(this.RMIDInfo[RMIDINFOChunks.bankOffset])
|
|
131
117
|
{
|
|
132
|
-
this.bankOffset =
|
|
118
|
+
this.bankOffset = readLittleEndian(this.RMIDInfo[RMIDINFOChunks.bankOffset], 2);
|
|
133
119
|
}
|
|
134
120
|
}
|
|
135
121
|
}
|
|
136
122
|
}
|
|
123
|
+
|
|
124
|
+
if(DLSRMID)
|
|
125
|
+
{
|
|
126
|
+
console.log(DLSRMID)
|
|
127
|
+
// assume bank offset of 0 by default. If we find any bank selects, then the offset is 1.
|
|
128
|
+
this.bankOffset = 0;
|
|
129
|
+
}
|
|
137
130
|
}
|
|
138
131
|
else
|
|
139
132
|
{
|
|
@@ -413,6 +406,15 @@ class MIDI{
|
|
|
413
406
|
loopEnd = 0;
|
|
414
407
|
}
|
|
415
408
|
break;
|
|
409
|
+
|
|
410
|
+
case 0:
|
|
411
|
+
// check RMID
|
|
412
|
+
if(DLSRMID && eventData[1] !== 0 && eventData[1] !== 127)
|
|
413
|
+
{
|
|
414
|
+
SpessaSynthInfo("%cDLS RMIDI with offset 1 detected!",
|
|
415
|
+
consoleColors.recognized);
|
|
416
|
+
this.bankOffset = 1;
|
|
417
|
+
}
|
|
416
418
|
}
|
|
417
419
|
}
|
|
418
420
|
}
|
|
@@ -564,25 +566,5 @@ class MIDI{
|
|
|
564
566
|
fileByteArray.currentIndex += chunk.size;
|
|
565
567
|
return chunk;
|
|
566
568
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Coverts ticks to time in seconds
|
|
571
|
-
* @param ticks {number}
|
|
572
|
-
* @returns {number}
|
|
573
|
-
* @private
|
|
574
|
-
*/
|
|
575
|
-
_ticksToSeconds(ticks)
|
|
576
|
-
{
|
|
577
|
-
if (ticks <= 0) {
|
|
578
|
-
return 0;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// find the last tempo change that has occured
|
|
582
|
-
let tempo = this.tempoChanges.find(v => v.ticks < ticks);
|
|
583
|
-
|
|
584
|
-
let timeSinceLastTempo = ticks - tempo.ticks;
|
|
585
|
-
return this._ticksToSeconds(ticks - timeSinceLastTempo) + (timeSinceLastTempo * 60) / (tempo.tempo * this.timeDivision);
|
|
586
|
-
}
|
|
587
569
|
}
|
|
588
570
|
export { MIDI }
|
|
@@ -4,7 +4,7 @@ import { writeBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Exports the midi as a .mid file
|
|
7
|
-
* @param midi {
|
|
7
|
+
* @param midi {BasicMIDI}
|
|
8
8
|
* @returns {Uint8Array} the binary .mid file data
|
|
9
9
|
*/
|
|
10
10
|
export function writeMIDIFile(midi)
|
|
@@ -47,8 +47,8 @@ const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
|
|
|
47
47
|
/**
|
|
48
48
|
* Writes an RMIDI file
|
|
49
49
|
* @param soundfontBinary {Uint8Array}
|
|
50
|
-
* @param mid {
|
|
51
|
-
* @param soundfont {
|
|
50
|
+
* @param mid {BasicMIDI}
|
|
51
|
+
* @param soundfont {BasicSoundFont}
|
|
52
52
|
* @param bankOffset {number} the bank offset for RMIDI
|
|
53
53
|
* @param encoding {string} the encoding of the RMIDI info chunk
|
|
54
54
|
* @param metadata {RMIDMetadata} the metadata of the file. Optional. If provided, the encoding is forced to utf-8/
|
|
@@ -4,7 +4,7 @@ import { DEFAULT_PERCUSSION } from '../synthetizer/synthetizer.js'
|
|
|
4
4
|
import { messageTypes, midiControllers } from './midi_message.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @param mid {
|
|
7
|
+
* @param mid {BasicMIDI}
|
|
8
8
|
* @param soundfont {{getPreset: function(number, number): BasicPreset}}
|
|
9
9
|
* @returns {Object<string, Set<string>>}
|
|
10
10
|
*/
|
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -35,7 +35,7 @@ export function assignMIDIPort(trackNum, port)
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Loads a new sequence
|
|
38
|
-
* @param parsedMidi {
|
|
38
|
+
* @param parsedMidi {BasicMIDI}
|
|
39
39
|
* @this {WorkletSequencer}
|
|
40
40
|
*/
|
|
41
41
|
export function loadNewSequence(parsedMidi)
|
|
@@ -49,7 +49,7 @@ export function loadNewSequence(parsedMidi)
|
|
|
49
49
|
this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision)
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* @type {
|
|
52
|
+
* @type {BasicMIDI}
|
|
53
53
|
*/
|
|
54
54
|
this.midiData = parsedMidi;
|
|
55
55
|
|
|
@@ -135,7 +135,7 @@ export function loadNewSongList(midiBuffers)
|
|
|
135
135
|
{
|
|
136
136
|
/**
|
|
137
137
|
* parse the MIDIs (only the array buffers, MIDI is unchanged)
|
|
138
|
-
* @type {
|
|
138
|
+
* @type {BasicMIDI[]}
|
|
139
139
|
*/
|
|
140
140
|
this.songs = midiBuffers.reduce((mids, b) => {
|
|
141
141
|
if(b.duration)
|
package/soundfont/README.md
CHANGED
|
@@ -3,6 +3,10 @@ The code here is responsible for parsing the SoundFont2 file and
|
|
|
3
3
|
providing an easy way to get the data out.
|
|
4
4
|
Default modulators are also stored here (in `modulators.js`)
|
|
5
5
|
|
|
6
|
-
`
|
|
6
|
+
`basic_soundfont` folder contains the classes that represent the soundfont file.
|
|
7
7
|
|
|
8
|
-
`
|
|
8
|
+
`read_sf2` folder contains the code for reading an `.sf2` file.
|
|
9
|
+
|
|
10
|
+
`write` folder contains the code for writing out an `.sf2` file.
|
|
11
|
+
|
|
12
|
+
`dls` folder contains the code for reading a `.dls` file (and converting in into a soundfont representation).
|
|
@@ -58,15 +58,15 @@ export class BasicSample {
|
|
|
58
58
|
*/
|
|
59
59
|
this.sampleType = sampleType
|
|
60
60
|
/**
|
|
61
|
-
* Relative to start of the sample, bytes
|
|
61
|
+
* Relative to start of the sample, bytes assuming 16 bit
|
|
62
62
|
* @type {number}
|
|
63
63
|
*/
|
|
64
64
|
this.sampleLoopStartIndex = loopStart
|
|
65
65
|
/**
|
|
66
|
-
* Relative to start of the sample, in bytes
|
|
66
|
+
* Relative to start of the sample, in bytes assuming 16 bit
|
|
67
67
|
* @type {number}
|
|
68
68
|
*/
|
|
69
|
-
this.sampleLoopEndIndex = loopEnd
|
|
69
|
+
this.sampleLoopEndIndex = loopEnd;
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Indicates if the sample is compressed
|
|
@@ -6,34 +6,34 @@
|
|
|
6
6
|
|
|
7
7
|
export class BasicZone
|
|
8
8
|
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
this.generators = [];
|
|
16
|
-
/**
|
|
17
|
-
* The zone's modulators
|
|
18
|
-
* @type {Modulator[]}
|
|
19
|
-
*/
|
|
20
|
-
this.modulators = [];
|
|
21
|
-
/**
|
|
22
|
-
* Indicates if the zone is global
|
|
23
|
-
* @type {boolean}
|
|
24
|
-
*/
|
|
25
|
-
this.isGlobal = false;
|
|
26
|
-
/**
|
|
27
|
-
* The zone's key range
|
|
28
|
-
* @type {SoundFontRange}
|
|
29
|
-
*/
|
|
30
|
-
this.keyRange = { min: 0, max: 127 };
|
|
9
|
+
/**
|
|
10
|
+
* The zone's velocity range
|
|
11
|
+
* @type {SoundFontRange}
|
|
12
|
+
*/
|
|
13
|
+
velRange = { min: 0, max: 127 };
|
|
31
14
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The zone's key range
|
|
17
|
+
* @type {SoundFontRange}
|
|
18
|
+
*/
|
|
19
|
+
keyRange = { min: 0, max: 127 };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Indicates if the zone is global
|
|
23
|
+
* @type {boolean}
|
|
24
|
+
*/
|
|
25
|
+
isGlobal = false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The zone's generators
|
|
29
|
+
* @type {Generator[]}
|
|
30
|
+
*/
|
|
31
|
+
generators = [];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The zone's modulators
|
|
35
|
+
* @type {Modulator[]}
|
|
36
|
+
*/
|
|
37
|
+
modulators = [];
|
|
38
38
|
}
|
|
39
39
|
|