spessasynth_core 3.27.8 → 4.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/README.md +85 -51
- package/dist/index.d.ts +4057 -0
- package/dist/index.js +17178 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -6
- package/index.js +0 -132
- package/src/externals/README.md +0 -6
- package/src/externals/fflate/LICENSE +0 -21
- package/src/externals/fflate/fflate.min.js +0 -1
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -12
- package/src/externals/stbvorbis_sync/LICENSE +0 -202
- package/src/externals/stbvorbis_sync/NOTICE +0 -6
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -1
- package/src/midi/README.md +0 -32
- package/src/midi/basic_midi.js +0 -587
- package/src/midi/midi_builder.js +0 -203
- package/src/midi/midi_loader.js +0 -321
- package/src/midi/midi_message.js +0 -254
- package/src/midi/midi_sequence.js +0 -230
- package/src/midi/midi_tools/get_note_times.js +0 -154
- package/src/midi/midi_tools/midi_editor.js +0 -611
- package/src/midi/midi_tools/midi_writer.js +0 -105
- package/src/midi/midi_tools/rmidi_writer.js +0 -566
- package/src/midi/midi_tools/used_keys_loaded.js +0 -256
- package/src/midi/xmf_loader.js +0 -454
- package/src/sequencer/README.md +0 -9
- package/src/sequencer/events.js +0 -81
- package/src/sequencer/play.js +0 -362
- package/src/sequencer/process_event.js +0 -165
- package/src/sequencer/process_tick.js +0 -104
- package/src/sequencer/sequencer_engine.js +0 -372
- package/src/sequencer/song_control.js +0 -196
- package/src/soundfont/README.md +0 -11
- package/src/soundfont/basic_soundfont/basic_global_zone.js +0 -6
- package/src/soundfont/basic_soundfont/basic_instrument.js +0 -115
- package/src/soundfont/basic_soundfont/basic_instrument_zone.js +0 -45
- package/src/soundfont/basic_soundfont/basic_preset.js +0 -313
- package/src/soundfont/basic_soundfont/basic_preset_zone.js +0 -39
- package/src/soundfont/basic_soundfont/basic_sample.js +0 -477
- package/src/soundfont/basic_soundfont/basic_soundbank.js +0 -740
- package/src/soundfont/basic_soundfont/basic_zone.js +0 -145
- package/src/soundfont/basic_soundfont/generator.js +0 -76
- package/src/soundfont/basic_soundfont/generator_types.js +0 -151
- package/src/soundfont/basic_soundfont/modulator.js +0 -581
- package/src/soundfont/basic_soundfont/riff_chunk.js +0 -195
- package/src/soundfont/basic_soundfont/write_dls/art2.js +0 -174
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +0 -49
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +0 -374
- package/src/soundfont/basic_soundfont/write_dls/ins.js +0 -85
- package/src/soundfont/basic_soundfont/write_dls/lins.js +0 -15
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +0 -330
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +0 -120
- package/src/soundfont/basic_soundfont/write_dls/wave.js +0 -71
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +0 -124
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +0 -78
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +0 -35
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +0 -60
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +0 -91
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +0 -62
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +0 -42
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +0 -57
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +0 -92
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +0 -61
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +0 -62
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +0 -131
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +0 -77
- package/src/soundfont/basic_soundfont/write_sf2/write.js +0 -287
- package/src/soundfont/dls/articulator_converter.js +0 -402
- package/src/soundfont/dls/dls_destinations.js +0 -38
- package/src/soundfont/dls/dls_instrument.js +0 -20
- package/src/soundfont/dls/dls_preset.js +0 -43
- package/src/soundfont/dls/dls_sample.js +0 -238
- package/src/soundfont/dls/dls_soundfont.js +0 -183
- package/src/soundfont/dls/dls_sources.js +0 -63
- package/src/soundfont/dls/dls_zone.js +0 -89
- package/src/soundfont/dls/read_articulation.js +0 -300
- package/src/soundfont/dls/read_instrument.js +0 -118
- package/src/soundfont/dls/read_instrument_list.js +0 -17
- package/src/soundfont/dls/read_lart.js +0 -35
- package/src/soundfont/dls/read_region.js +0 -157
- package/src/soundfont/dls/read_samples.js +0 -154
- package/src/soundfont/load_soundfont.js +0 -21
- package/src/soundfont/read_sf2/generators.js +0 -43
- package/src/soundfont/read_sf2/instrument_zones.js +0 -75
- package/src/soundfont/read_sf2/instruments.js +0 -71
- package/src/soundfont/read_sf2/modulators.js +0 -25
- package/src/soundfont/read_sf2/preset_zones.js +0 -79
- package/src/soundfont/read_sf2/presets.js +0 -80
- package/src/soundfont/read_sf2/samples.js +0 -317
- package/src/soundfont/read_sf2/soundfont.js +0 -452
- package/src/soundfont/read_sf2/zones.js +0 -28
- package/src/synthetizer/README.md +0 -7
- package/src/synthetizer/audio_engine/README.md +0 -9
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +0 -289
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +0 -90
- package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +0 -95
- package/src/synthetizer/audio_engine/engine_components/enums.js +0 -18
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +0 -151
- package/src/synthetizer/audio_engine/engine_components/lfo.js +0 -26
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +0 -282
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +0 -551
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +0 -181
- package/src/synthetizer/audio_engine/engine_components/modulator_curves.js +0 -89
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +0 -265
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +0 -124
- package/src/synthetizer/audio_engine/engine_components/unit_converter.js +0 -73
- package/src/synthetizer/audio_engine/engine_components/voice.js +0 -525
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +0 -402
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +0 -274
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +0 -159
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +0 -53
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +0 -254
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +0 -20
- package/src/synthetizer/audio_engine/engine_methods/data_entry/awe32.js +0 -198
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +0 -281
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +0 -109
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +0 -17
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +0 -220
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +0 -92
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +0 -35
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +0 -214
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/embedded_sound_bank.js +0 -42
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/update_preset_list.js +0 -19
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +0 -23
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +0 -56
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +0 -16
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +0 -30
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +0 -63
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +0 -1058
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +0 -23
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +0 -31
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +0 -29
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +0 -15
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +0 -26
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +0 -19
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +0 -27
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +0 -15
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +0 -34
- package/src/synthetizer/audio_engine/main_processor.js +0 -817
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +0 -16
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +0 -175
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +0 -116
- package/src/synthetizer/audio_engine/synth_processor_options.js +0 -18
- package/src/synthetizer/synth_constants.js +0 -26
- package/src/utils/README.md +0 -8
- package/src/utils/buffer_to_wav.js +0 -197
- package/src/utils/byte_functions/big_endian.js +0 -32
- package/src/utils/byte_functions/little_endian.js +0 -77
- package/src/utils/byte_functions/string.js +0 -92
- package/src/utils/byte_functions/variable_length_quantity.js +0 -42
- package/src/utils/fill_with_defaults.js +0 -21
- package/src/utils/indexed_array.js +0 -34
- package/src/utils/loggin.js +0 -71
- package/src/utils/other.js +0 -92
- package/src/utils/sysex_detector.js +0 -58
- package/src/utils/xg_hacks.js +0 -193
package/src/midi/basic_midi.js
DELETED
|
@@ -1,587 +0,0 @@
|
|
|
1
|
-
// noinspection JSUnusedGlobalSymbols
|
|
2
|
-
|
|
3
|
-
import { MIDISequenceData } from "./midi_sequence.js";
|
|
4
|
-
import { getStringBytes, readBytesAsString } from "../utils/byte_functions/string.js";
|
|
5
|
-
import { messageTypes, MIDIMessage } from "./midi_message.js";
|
|
6
|
-
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
7
|
-
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../utils/loggin.js";
|
|
8
|
-
import { consoleColors, formatTitle, sanitizeKarLyrics } from "../utils/other.js";
|
|
9
|
-
import { writeMIDI } from "./midi_tools/midi_writer.js";
|
|
10
|
-
import { applySnapshotToMIDI, modifyMIDI } from "./midi_tools/midi_editor.js";
|
|
11
|
-
import { writeRMIDI } from "./midi_tools/rmidi_writer.js";
|
|
12
|
-
import { getUsedProgramsAndKeys } from "./midi_tools/used_keys_loaded.js";
|
|
13
|
-
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
14
|
-
import { getNoteTimes } from "./midi_tools/get_note_times.js";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* BasicMIDI is the base of a complete MIDI file, used by the sequencer internally.
|
|
18
|
-
* BasicMIDI is not available on the main thread, as it contains the actual track data which can be large.
|
|
19
|
-
* It can be accessed by calling getMIDI() on the Sequencer.
|
|
20
|
-
*/
|
|
21
|
-
class BasicMIDI extends MIDISequenceData
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The embedded soundfont in the MIDI file, represented as an ArrayBuffer, if available.
|
|
26
|
-
* @type {ArrayBuffer|undefined}
|
|
27
|
-
*/
|
|
28
|
-
embeddedSoundFont = undefined;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The actual track data of the MIDI file, represented as an array of tracks.
|
|
32
|
-
* Tracks are arrays of MIDIMessage objects.
|
|
33
|
-
* @type {MIDIMessage[][]}
|
|
34
|
-
*/
|
|
35
|
-
tracks = [];
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* If the MIDI file is a DLS RMIDI file.
|
|
39
|
-
* @type {boolean}
|
|
40
|
-
*/
|
|
41
|
-
isDLSRMIDI = false;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Copies a MIDI (tracks are shallowly copied!)
|
|
45
|
-
* @param mid {BasicMIDI}
|
|
46
|
-
* @returns {BasicMIDI}
|
|
47
|
-
*/
|
|
48
|
-
static copyFrom(mid)
|
|
49
|
-
{
|
|
50
|
-
const m = new BasicMIDI();
|
|
51
|
-
m._copyFromSequence(mid);
|
|
52
|
-
|
|
53
|
-
m.isDLSRMIDI = mid.isDLSRMIDI;
|
|
54
|
-
m.embeddedSoundFont = mid?.embeddedSoundFont ? mid.embeddedSoundFont : undefined; // Shallow copy
|
|
55
|
-
m.tracks = mid.tracks.map(track => [...track]); // Shallow copy of each track array
|
|
56
|
-
return m;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Copies a MIDI with deep copy
|
|
61
|
-
* @param mid {BasicMIDI}
|
|
62
|
-
* @returns {BasicMIDI}
|
|
63
|
-
*/
|
|
64
|
-
static copyFromDeep(mid)
|
|
65
|
-
{
|
|
66
|
-
const m = new BasicMIDI();
|
|
67
|
-
m._copyFromSequence(mid);
|
|
68
|
-
m.isDLSRMIDI = mid.isDLSRMIDI;
|
|
69
|
-
m.embeddedSoundFont = mid.embeddedSoundFont ? mid.embeddedSoundFont.slice(0) : undefined; // Deep copy
|
|
70
|
-
m.tracks = mid.tracks.map(track => track.map(event => new MIDIMessage(
|
|
71
|
-
event.ticks,
|
|
72
|
-
event.messageStatusByte,
|
|
73
|
-
event.messageData
|
|
74
|
-
))); // Deep copy
|
|
75
|
-
return m;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Parses internal MIDI values
|
|
80
|
-
* @protected
|
|
81
|
-
*/
|
|
82
|
-
_parseInternal()
|
|
83
|
-
{
|
|
84
|
-
SpessaSynthGroup(
|
|
85
|
-
"%cInterpreting MIDI events...",
|
|
86
|
-
consoleColors.info
|
|
87
|
-
);
|
|
88
|
-
/**
|
|
89
|
-
* For karaoke files, text events starting with @T are considered titles,
|
|
90
|
-
* usually the first one is the title, and the latter is things such as "sequenced by" etc.
|
|
91
|
-
* @type {boolean}
|
|
92
|
-
*/
|
|
93
|
-
let karaokeHasTitle = false;
|
|
94
|
-
|
|
95
|
-
this.keyRange = { max: 0, min: 127 };
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Will be joined with "\n" to form the final string
|
|
99
|
-
* @type {string[]}
|
|
100
|
-
*/
|
|
101
|
-
let copyrightComponents = [];
|
|
102
|
-
let copyrightDetected = false;
|
|
103
|
-
if (typeof this.RMIDInfo["ICOP"] !== "undefined")
|
|
104
|
-
{
|
|
105
|
-
// if RMIDI has copyright info, don't try to detect one.
|
|
106
|
-
copyrightDetected = true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let nameDetected = false;
|
|
111
|
-
if (typeof this.RMIDInfo["INAM"] !== "undefined")
|
|
112
|
-
{
|
|
113
|
-
// same as with copyright
|
|
114
|
-
nameDetected = true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// loop tracking
|
|
118
|
-
let loopStart = null;
|
|
119
|
-
let loopEnd = null;
|
|
120
|
-
|
|
121
|
-
for (let i = 0; i < this.tracks.length; i++)
|
|
122
|
-
{
|
|
123
|
-
/**
|
|
124
|
-
* @type {MIDIMessage[]}
|
|
125
|
-
*/
|
|
126
|
-
const track = this.tracks[i];
|
|
127
|
-
const usedChannels = new Set();
|
|
128
|
-
let trackHasVoiceMessages = false;
|
|
129
|
-
|
|
130
|
-
for (const e of track)
|
|
131
|
-
{
|
|
132
|
-
// check if it's a voice message
|
|
133
|
-
if (e.messageStatusByte >= 0x80 && e.messageStatusByte < 0xF0)
|
|
134
|
-
{
|
|
135
|
-
trackHasVoiceMessages = true;
|
|
136
|
-
// voice messages are 7-bit always
|
|
137
|
-
for (let j = 0; j < e.messageData.length; j++)
|
|
138
|
-
{
|
|
139
|
-
e.messageData[j] = Math.min(127, e.messageData[j]);
|
|
140
|
-
}
|
|
141
|
-
// last voice event tick
|
|
142
|
-
if (e.ticks > this.lastVoiceEventTick)
|
|
143
|
-
{
|
|
144
|
-
this.lastVoiceEventTick = e.ticks;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// interpret the voice message
|
|
148
|
-
switch (e.messageStatusByte & 0xF0)
|
|
149
|
-
{
|
|
150
|
-
// cc change: loop points
|
|
151
|
-
case messageTypes.controllerChange:
|
|
152
|
-
switch (e.messageData[0])
|
|
153
|
-
{
|
|
154
|
-
case 2:
|
|
155
|
-
case 116:
|
|
156
|
-
loopStart = e.ticks;
|
|
157
|
-
break;
|
|
158
|
-
|
|
159
|
-
case 4:
|
|
160
|
-
case 117:
|
|
161
|
-
if (loopEnd === null)
|
|
162
|
-
{
|
|
163
|
-
loopEnd = e.ticks;
|
|
164
|
-
}
|
|
165
|
-
else
|
|
166
|
-
{
|
|
167
|
-
// this controller has occurred more than once;
|
|
168
|
-
// this means
|
|
169
|
-
// that it doesn't indicate the loop
|
|
170
|
-
loopEnd = 0;
|
|
171
|
-
}
|
|
172
|
-
break;
|
|
173
|
-
|
|
174
|
-
case 0:
|
|
175
|
-
// check RMID
|
|
176
|
-
if (this.isDLSRMIDI && e.messageData[1] !== 0 && e.messageData[1] !== 127)
|
|
177
|
-
{
|
|
178
|
-
SpessaSynthInfo(
|
|
179
|
-
"%cDLS RMIDI with offset 1 detected!",
|
|
180
|
-
consoleColors.recognized
|
|
181
|
-
);
|
|
182
|
-
this.bankOffset = 1;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
|
|
187
|
-
// note on: used notes tracking and key range
|
|
188
|
-
case messageTypes.noteOn:
|
|
189
|
-
usedChannels.add(e.messageStatusByte & 0x0F);
|
|
190
|
-
const note = e.messageData[0];
|
|
191
|
-
this.keyRange.min = Math.min(this.keyRange.min, note);
|
|
192
|
-
this.keyRange.max = Math.max(this.keyRange.max, note);
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
e.messageData.currentIndex = 0;
|
|
197
|
-
const eventText = readBytesAsString(e.messageData, e.messageData.length);
|
|
198
|
-
e.messageData.currentIndex = 0;
|
|
199
|
-
// interpret the message
|
|
200
|
-
switch (e.messageStatusByte)
|
|
201
|
-
{
|
|
202
|
-
case messageTypes.setTempo:
|
|
203
|
-
// add the tempo change
|
|
204
|
-
e.messageData.currentIndex = 0;
|
|
205
|
-
this.tempoChanges.push({
|
|
206
|
-
ticks: e.ticks,
|
|
207
|
-
tempo: 60000000 / readBytesAsUintBigEndian(e.messageData, 3)
|
|
208
|
-
});
|
|
209
|
-
e.messageData.currentIndex = 0;
|
|
210
|
-
break;
|
|
211
|
-
|
|
212
|
-
case messageTypes.marker:
|
|
213
|
-
// check for loop markers
|
|
214
|
-
const text = eventText.trim().toLowerCase();
|
|
215
|
-
switch (text)
|
|
216
|
-
{
|
|
217
|
-
default:
|
|
218
|
-
break;
|
|
219
|
-
|
|
220
|
-
case "start":
|
|
221
|
-
case "loopstart":
|
|
222
|
-
loopStart = e.ticks;
|
|
223
|
-
break;
|
|
224
|
-
|
|
225
|
-
case "loopend":
|
|
226
|
-
loopEnd = e.ticks;
|
|
227
|
-
}
|
|
228
|
-
e.messageData.currentIndex = 0;
|
|
229
|
-
break;
|
|
230
|
-
|
|
231
|
-
case messageTypes.copyright:
|
|
232
|
-
if (!copyrightDetected)
|
|
233
|
-
{
|
|
234
|
-
e.messageData.currentIndex = 0;
|
|
235
|
-
copyrightComponents.push(readBytesAsString(
|
|
236
|
-
e.messageData,
|
|
237
|
-
e.messageData.length,
|
|
238
|
-
false
|
|
239
|
-
));
|
|
240
|
-
e.messageData.currentIndex = 0;
|
|
241
|
-
}
|
|
242
|
-
break;
|
|
243
|
-
|
|
244
|
-
case messageTypes.lyric:
|
|
245
|
-
// note here: .kar files sometimes just use...
|
|
246
|
-
// lyrics instead of text because why not (of course)
|
|
247
|
-
// perform the same check for @KMIDI KARAOKE FILE
|
|
248
|
-
if (eventText.trim().startsWith("@KMIDI KARAOKE FILE"))
|
|
249
|
-
{
|
|
250
|
-
this.isKaraokeFile = true;
|
|
251
|
-
SpessaSynthInfo("%cKaraoke MIDI detected!", consoleColors.recognized);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (this.isKaraokeFile)
|
|
255
|
-
{
|
|
256
|
-
// replace the type of the message with text
|
|
257
|
-
e.messageStatusByte = messageTypes.text;
|
|
258
|
-
}
|
|
259
|
-
else
|
|
260
|
-
{
|
|
261
|
-
// add lyrics like a regular midi file
|
|
262
|
-
this.lyrics.push(e.messageData);
|
|
263
|
-
this.lyricsTicks.push(e.ticks);
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// kar: treat the same as text
|
|
268
|
-
// fallthrough
|
|
269
|
-
case messageTypes.text:
|
|
270
|
-
// possibly Soft Karaoke MIDI file
|
|
271
|
-
// it has a text event at the start of the file
|
|
272
|
-
// "@KMIDI KARAOKE FILE"
|
|
273
|
-
const checkedText = eventText.trim();
|
|
274
|
-
if (checkedText.startsWith("@KMIDI KARAOKE FILE"))
|
|
275
|
-
{
|
|
276
|
-
this.isKaraokeFile = true;
|
|
277
|
-
|
|
278
|
-
SpessaSynthInfo("%cKaraoke MIDI detected!", consoleColors.recognized);
|
|
279
|
-
}
|
|
280
|
-
else if (this.isKaraokeFile)
|
|
281
|
-
{
|
|
282
|
-
// check for @T (title)
|
|
283
|
-
// or @A because it is a title too sometimes?
|
|
284
|
-
// IDK it's strange
|
|
285
|
-
if (checkedText.startsWith("@T") || checkedText.startsWith("@A"))
|
|
286
|
-
{
|
|
287
|
-
if (!karaokeHasTitle)
|
|
288
|
-
{
|
|
289
|
-
this.midiName = checkedText.substring(2).trim();
|
|
290
|
-
karaokeHasTitle = true;
|
|
291
|
-
nameDetected = true;
|
|
292
|
-
// encode to rawMidiName
|
|
293
|
-
this.rawMidiName = getStringBytes(this.midiName);
|
|
294
|
-
}
|
|
295
|
-
else
|
|
296
|
-
{
|
|
297
|
-
// append to copyright
|
|
298
|
-
copyrightComponents.push(checkedText.substring(2).trim());
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else if (checkedText[0] !== "@")
|
|
302
|
-
{
|
|
303
|
-
// non @: the lyrics
|
|
304
|
-
this.lyrics.push(sanitizeKarLyrics(e.messageData));
|
|
305
|
-
this.lyricsTicks.push(e.ticks);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
break;
|
|
309
|
-
|
|
310
|
-
case messageTypes.trackName:
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
// add used channels
|
|
315
|
-
this.usedChannelsOnTrack.push(usedChannels);
|
|
316
|
-
|
|
317
|
-
// track name
|
|
318
|
-
this.trackNames[i] = "";
|
|
319
|
-
const trackName = track.find(e => e.messageStatusByte === messageTypes.trackName);
|
|
320
|
-
if (trackName)
|
|
321
|
-
{
|
|
322
|
-
trackName.messageData.currentIndex = 0;
|
|
323
|
-
const name = readBytesAsString(trackName.messageData, trackName.messageData.length);
|
|
324
|
-
this.trackNames[i] = name;
|
|
325
|
-
// If the track has no voice messages, its "track name" event (if it has any)
|
|
326
|
-
// is some metadata.
|
|
327
|
-
// Add it to copyright
|
|
328
|
-
if (!trackHasVoiceMessages)
|
|
329
|
-
{
|
|
330
|
-
copyrightComponents.push(name);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// reverse the tempo changes
|
|
336
|
-
this.tempoChanges.reverse();
|
|
337
|
-
|
|
338
|
-
SpessaSynthInfo(
|
|
339
|
-
`%cCorrecting loops, ports and detecting notes...`,
|
|
340
|
-
consoleColors.info
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
const firstNoteOns = [];
|
|
344
|
-
for (const t of this.tracks)
|
|
345
|
-
{
|
|
346
|
-
const firstNoteOn = t.find(e => (e.messageStatusByte & 0xF0) === messageTypes.noteOn);
|
|
347
|
-
if (firstNoteOn)
|
|
348
|
-
{
|
|
349
|
-
firstNoteOns.push(firstNoteOn.ticks);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
this.firstNoteOn = Math.min(...firstNoteOns);
|
|
353
|
-
|
|
354
|
-
SpessaSynthInfo(
|
|
355
|
-
`%cFirst note-on detected at: %c${this.firstNoteOn}%c ticks!`,
|
|
356
|
-
consoleColors.info,
|
|
357
|
-
consoleColors.recognized,
|
|
358
|
-
consoleColors.info
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (loopStart !== null && loopEnd === null)
|
|
363
|
-
{
|
|
364
|
-
// not a loop
|
|
365
|
-
loopStart = this.firstNoteOn;
|
|
366
|
-
loopEnd = this.lastVoiceEventTick;
|
|
367
|
-
}
|
|
368
|
-
else
|
|
369
|
-
{
|
|
370
|
-
if (loopStart === null)
|
|
371
|
-
{
|
|
372
|
-
loopStart = this.firstNoteOn;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (loopEnd === null || loopEnd === 0)
|
|
376
|
-
{
|
|
377
|
-
loopEnd = this.lastVoiceEventTick;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
*
|
|
383
|
-
* @type {{start: number, end: number}}
|
|
384
|
-
*/
|
|
385
|
-
this.loop = { start: loopStart, end: loopEnd };
|
|
386
|
-
|
|
387
|
-
SpessaSynthInfo(
|
|
388
|
-
`%cLoop points: start: %c${this.loop.start}%c end: %c${this.loop.end}`,
|
|
389
|
-
consoleColors.info,
|
|
390
|
-
consoleColors.recognized,
|
|
391
|
-
consoleColors.info,
|
|
392
|
-
consoleColors.recognized
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
// determine ports
|
|
396
|
-
let portOffset = 0;
|
|
397
|
-
this.midiPorts = [];
|
|
398
|
-
this.midiPortChannelOffsets = [];
|
|
399
|
-
for (let trackNum = 0; trackNum < this.tracks.length; trackNum++)
|
|
400
|
-
{
|
|
401
|
-
this.midiPorts.push(-1);
|
|
402
|
-
if (this.usedChannelsOnTrack[trackNum].size === 0)
|
|
403
|
-
{
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
for (const e of this.tracks[trackNum])
|
|
407
|
-
{
|
|
408
|
-
if (e.messageStatusByte !== messageTypes.midiPort)
|
|
409
|
-
{
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
const port = e.messageData[0];
|
|
413
|
-
this.midiPorts[trackNum] = port;
|
|
414
|
-
if (this.midiPortChannelOffsets[port] === undefined)
|
|
415
|
-
{
|
|
416
|
-
this.midiPortChannelOffsets[port] = portOffset;
|
|
417
|
-
portOffset += 16;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// fix empty port channel offsets (do a copy to turn empty slots into undefined so the map goes over them)
|
|
423
|
-
this.midiPortChannelOffsets = [...this.midiPortChannelOffsets].map(o => o ?? 0);
|
|
424
|
-
|
|
425
|
-
// fix midi ports:
|
|
426
|
-
// midi tracks without ports will have a value of -1
|
|
427
|
-
// if all ports have a value of -1, set it to 0,
|
|
428
|
-
// otherwise take the first midi port and replace all -1 with it,
|
|
429
|
-
// why would we do this?
|
|
430
|
-
// some midis (for some reason) specify all channels to port 1 or else,
|
|
431
|
-
// but leave the conductor track with no port pref.
|
|
432
|
-
// this spessasynth to reserve the first 16 channels for the conductor track
|
|
433
|
-
// (which doesn't play anything) and use the additional 16 for the actual ports.
|
|
434
|
-
let defaultPort = Infinity;
|
|
435
|
-
for (let port of this.midiPorts)
|
|
436
|
-
{
|
|
437
|
-
if (port !== -1)
|
|
438
|
-
{
|
|
439
|
-
if (defaultPort > port)
|
|
440
|
-
{
|
|
441
|
-
defaultPort = port;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (defaultPort === Infinity)
|
|
446
|
-
{
|
|
447
|
-
defaultPort = 0;
|
|
448
|
-
}
|
|
449
|
-
this.midiPorts = this.midiPorts.map(port => port === -1 || port === undefined ? defaultPort : port);
|
|
450
|
-
// add fake port if empty
|
|
451
|
-
if (this.midiPortChannelOffsets.length === 0)
|
|
452
|
-
{
|
|
453
|
-
this.midiPortChannelOffsets = [0];
|
|
454
|
-
}
|
|
455
|
-
if (this.midiPortChannelOffsets.length < 2)
|
|
456
|
-
{
|
|
457
|
-
SpessaSynthInfo(`%cNo additional MIDI Ports detected.`, consoleColors.info);
|
|
458
|
-
}
|
|
459
|
-
else
|
|
460
|
-
{
|
|
461
|
-
this.isMultiPort = true;
|
|
462
|
-
SpessaSynthInfo(`%cMIDI Ports detected!`, consoleColors.recognized);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// midi name
|
|
466
|
-
if (!nameDetected)
|
|
467
|
-
{
|
|
468
|
-
if (this.tracks.length > 1)
|
|
469
|
-
{
|
|
470
|
-
// if more than 1 track and the first track has no notes,
|
|
471
|
-
// just find the first trackName in the first track.
|
|
472
|
-
if (
|
|
473
|
-
this.tracks[0].find(
|
|
474
|
-
message => message.messageStatusByte >= messageTypes.noteOn
|
|
475
|
-
&&
|
|
476
|
-
message.messageStatusByte < messageTypes.polyPressure
|
|
477
|
-
) === undefined
|
|
478
|
-
)
|
|
479
|
-
{
|
|
480
|
-
|
|
481
|
-
let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
|
|
482
|
-
if (name)
|
|
483
|
-
{
|
|
484
|
-
this.rawMidiName = name.messageData;
|
|
485
|
-
name.messageData.currentIndex = 0;
|
|
486
|
-
this.midiName = readBytesAsString(name.messageData, name.messageData.length, false);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
else
|
|
491
|
-
{
|
|
492
|
-
// if only 1 track, find the first "track name" event
|
|
493
|
-
let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
|
|
494
|
-
if (name)
|
|
495
|
-
{
|
|
496
|
-
this.rawMidiName = name.messageData;
|
|
497
|
-
name.messageData.currentIndex = 0;
|
|
498
|
-
this.midiName = readBytesAsString(name.messageData, name.messageData.length, false);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (!copyrightDetected)
|
|
504
|
-
{
|
|
505
|
-
this.copyright = copyrightComponents
|
|
506
|
-
// trim and group newlines into one
|
|
507
|
-
.map(c => c.trim().replace(/(\r?\n)+/g, "\n"))
|
|
508
|
-
// remove empty strings
|
|
509
|
-
.filter(c => c.length > 0)
|
|
510
|
-
// join with newlines
|
|
511
|
-
.join("\n") || "";
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
this.midiName = this.midiName.trim();
|
|
515
|
-
this.midiNameUsesFileName = false;
|
|
516
|
-
// if midiName is "", use the file name
|
|
517
|
-
if (this.midiName.length === 0)
|
|
518
|
-
{
|
|
519
|
-
SpessaSynthInfo(
|
|
520
|
-
`%cNo name detected. Using the alt name!`,
|
|
521
|
-
consoleColors.info
|
|
522
|
-
);
|
|
523
|
-
this.midiName = formatTitle(this.fileName);
|
|
524
|
-
this.midiNameUsesFileName = true;
|
|
525
|
-
// encode it too
|
|
526
|
-
this.rawMidiName = new Uint8Array(this.midiName.length);
|
|
527
|
-
for (let i = 0; i < this.midiName.length; i++)
|
|
528
|
-
{
|
|
529
|
-
this.rawMidiName[i] = this.midiName.charCodeAt(i);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
else
|
|
533
|
-
{
|
|
534
|
-
SpessaSynthInfo(
|
|
535
|
-
`%cMIDI Name detected! %c"${this.midiName}"`,
|
|
536
|
-
consoleColors.info,
|
|
537
|
-
consoleColors.recognized
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// if the first event is not at 0 ticks, add a track name
|
|
542
|
-
// https://github.com/spessasus/SpessaSynth/issues/145
|
|
543
|
-
if (!this.tracks.some(t => t[0].ticks === 0))
|
|
544
|
-
{
|
|
545
|
-
const track = this.tracks[0];
|
|
546
|
-
// can copy
|
|
547
|
-
track.unshift(new MIDIMessage(
|
|
548
|
-
0,
|
|
549
|
-
messageTypes.trackName,
|
|
550
|
-
new IndexedByteArray(this.rawMidiName.buffer)
|
|
551
|
-
));
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* The total playback time, in seconds
|
|
557
|
-
* @type {number}
|
|
558
|
-
*/
|
|
559
|
-
this.duration = this.MIDIticksToSeconds(this.lastVoiceEventTick);
|
|
560
|
-
|
|
561
|
-
SpessaSynthInfo("%cSuccess!", consoleColors.recognized);
|
|
562
|
-
SpessaSynthGroupEnd();
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Updates all internal values
|
|
567
|
-
*/
|
|
568
|
-
flush()
|
|
569
|
-
{
|
|
570
|
-
|
|
571
|
-
for (const t of this.tracks)
|
|
572
|
-
{
|
|
573
|
-
// sort the track by ticks
|
|
574
|
-
t.sort((e1, e2) => e1.ticks - e2.ticks);
|
|
575
|
-
}
|
|
576
|
-
this._parseInternal();
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
BasicMIDI.prototype.writeMIDI = writeMIDI;
|
|
581
|
-
BasicMIDI.prototype.modifyMIDI = modifyMIDI;
|
|
582
|
-
BasicMIDI.prototype.applySnapshotToMIDI = applySnapshotToMIDI;
|
|
583
|
-
BasicMIDI.prototype.writeRMIDI = writeRMIDI;
|
|
584
|
-
BasicMIDI.prototype.getUsedProgramsAndKeys = getUsedProgramsAndKeys;
|
|
585
|
-
BasicMIDI.prototype.getNoteTimes = getNoteTimes;
|
|
586
|
-
|
|
587
|
-
export { BasicMIDI };
|