spessasynth_core 3.27.8 → 4.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.
- package/README.md +85 -51
- package/dist/index.d.ts +4057 -0
- package/dist/index.js +17188 -0
- package/dist/index.js.map +1 -0
- package/package.json +23 -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
|
@@ -1,105 +0,0 @@
|
|
|
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 standard MIDI file
|
|
7
|
-
* @this {BasicMIDI}
|
|
8
|
-
*/
|
|
9
|
-
export function writeMIDI()
|
|
10
|
-
{
|
|
11
|
-
const midi = this;
|
|
12
|
-
if (!midi.tracks)
|
|
13
|
-
{
|
|
14
|
-
throw new Error("MIDI has no tracks!");
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* @type {Uint8Array[]}
|
|
18
|
-
*/
|
|
19
|
-
const binaryTrackData = [];
|
|
20
|
-
for (const track of midi.tracks)
|
|
21
|
-
{
|
|
22
|
-
const binaryTrack = [];
|
|
23
|
-
let currentTick = 0;
|
|
24
|
-
let runningByte = undefined;
|
|
25
|
-
for (const event of track)
|
|
26
|
-
{
|
|
27
|
-
// Ticks stored in MIDI are absolute, but SMF wants relative. Convert them here.
|
|
28
|
-
const deltaTicks = event.ticks - currentTick;
|
|
29
|
-
/**
|
|
30
|
-
* @type {number[]}
|
|
31
|
-
*/
|
|
32
|
-
let messageData;
|
|
33
|
-
// determine the message
|
|
34
|
-
if (event.messageStatusByte <= messageTypes.sequenceSpecific)
|
|
35
|
-
{
|
|
36
|
-
// this is a meta-message
|
|
37
|
-
// syntax is FF<type><length><data>
|
|
38
|
-
messageData = [0xff, event.messageStatusByte, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
|
|
39
|
-
// RP-001:
|
|
40
|
-
// Sysex events and meta-events cancel any running status which was in effect.
|
|
41
|
-
runningByte = undefined;
|
|
42
|
-
}
|
|
43
|
-
else if (event.messageStatusByte === messageTypes.systemExclusive)
|
|
44
|
-
{
|
|
45
|
-
// this is a system exclusive message
|
|
46
|
-
// syntax is F0<length><data>
|
|
47
|
-
messageData = [0xf0, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
|
|
48
|
-
// RP-001:
|
|
49
|
-
// Sysex events and meta-events cancel any running status which was in effect.
|
|
50
|
-
runningByte = undefined;
|
|
51
|
-
}
|
|
52
|
-
else
|
|
53
|
-
{
|
|
54
|
-
// this is a midi message
|
|
55
|
-
messageData = [];
|
|
56
|
-
if (runningByte !== event.messageStatusByte)
|
|
57
|
-
{
|
|
58
|
-
// Running byte was not the byte we want. Add the byte here.
|
|
59
|
-
runningByte = event.messageStatusByte;
|
|
60
|
-
// add the status byte to the midi
|
|
61
|
-
messageData.push(event.messageStatusByte);
|
|
62
|
-
}
|
|
63
|
-
// add the data
|
|
64
|
-
messageData.push(...event.messageData);
|
|
65
|
-
}
|
|
66
|
-
// write VLQ
|
|
67
|
-
binaryTrack.push(...writeVariableLengthQuantity(deltaTicks));
|
|
68
|
-
// write the message
|
|
69
|
-
binaryTrack.push(...messageData);
|
|
70
|
-
currentTick += deltaTicks;
|
|
71
|
-
}
|
|
72
|
-
binaryTrackData.push(new Uint8Array(binaryTrack));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param text {string}
|
|
77
|
-
* @param arr {number[]}
|
|
78
|
-
*/
|
|
79
|
-
function writeText(text, arr)
|
|
80
|
-
{
|
|
81
|
-
for (let i = 0; i < text.length; i++)
|
|
82
|
-
{
|
|
83
|
-
arr.push(text.charCodeAt(i));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// write the file
|
|
88
|
-
const binaryData = [];
|
|
89
|
-
// write header
|
|
90
|
-
writeText("MThd", binaryData); // MThd
|
|
91
|
-
binaryData.push(...writeBytesAsUintBigEndian(6, 4)); // length
|
|
92
|
-
binaryData.push(0, midi.format); // format
|
|
93
|
-
binaryData.push(...writeBytesAsUintBigEndian(midi.tracksAmount, 2)); // num tracks
|
|
94
|
-
binaryData.push(...writeBytesAsUintBigEndian(midi.timeDivision, 2)); // time division
|
|
95
|
-
|
|
96
|
-
// write tracks
|
|
97
|
-
for (const track of binaryTrackData)
|
|
98
|
-
{
|
|
99
|
-
// write track header
|
|
100
|
-
writeText("MTrk", binaryData); // MTrk
|
|
101
|
-
binaryData.push(...writeBytesAsUintBigEndian(track.length, 4)); // length
|
|
102
|
-
binaryData.push(...track); // write data
|
|
103
|
-
}
|
|
104
|
-
return new Uint8Array(binaryData);
|
|
105
|
-
}
|
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
2
|
-
import { writeRIFFChunkParts, writeRIFFChunkRaw } from "../../soundfont/basic_soundfont/riff_chunk.js";
|
|
3
|
-
import { getStringBytes } from "../../utils/byte_functions/string.js";
|
|
4
|
-
import { messageTypes, midiControllers, MIDIMessage } from "../midi_message.js";
|
|
5
|
-
import { getGsOn } from "./midi_editor.js";
|
|
6
|
-
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
|
|
7
|
-
import { consoleColors } from "../../utils/other.js";
|
|
8
|
-
import { writeLittleEndian } from "../../utils/byte_functions/little_endian.js";
|
|
9
|
-
import { DEFAULT_PERCUSSION } from "../../synthetizer/synth_constants.js";
|
|
10
|
-
import { chooseBank, isSystemXG, parseBankSelect } from "../../utils/xg_hacks.js";
|
|
11
|
-
import { isGM2On, isGMOn, isGSDrumsOn, isGSOn, isXGOn } from "../../utils/sysex_detector.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @enum {string}
|
|
15
|
-
*/
|
|
16
|
-
export const RMIDINFOChunks = {
|
|
17
|
-
name: "INAM",
|
|
18
|
-
album: "IPRD",
|
|
19
|
-
album2: "IALB",
|
|
20
|
-
artist: "IART",
|
|
21
|
-
genre: "IGNR",
|
|
22
|
-
picture: "IPIC",
|
|
23
|
-
copyright: "ICOP",
|
|
24
|
-
creationDate: "ICRD",
|
|
25
|
-
comment: "ICMT",
|
|
26
|
-
engineer: "IENG",
|
|
27
|
-
software: "ISFT",
|
|
28
|
-
encoding: "IENC",
|
|
29
|
-
midiEncoding: "MENC",
|
|
30
|
-
bankOffset: "DBNK"
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const FORCED_ENCODING = "utf-8";
|
|
34
|
-
const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @typedef {Object} RMIDMetadata
|
|
38
|
-
* @property {string} name - the name of the file
|
|
39
|
-
* @property {string} engineer - the engineer who worked on the file
|
|
40
|
-
* @property {string} artist - the artist
|
|
41
|
-
* @property {string} album - the album
|
|
42
|
-
* @property {string} genre - the genre of the song
|
|
43
|
-
* @property {ArrayBuffer} picture - the image for the file (album cover)
|
|
44
|
-
* @property {string} comment - the coment of the file
|
|
45
|
-
* @property {string} creationDate - the creation date of the file
|
|
46
|
-
* @property {string} copyright - the copyright of the file
|
|
47
|
-
* @property {string} midiEncoding - the encoding of the inner MIDI file
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Writes an RMIDI file. Note that this method modifies the MIDI file in-place.
|
|
52
|
-
* @this {BasicMIDI}
|
|
53
|
-
* @param soundfontBinary {Uint8Array}
|
|
54
|
-
* @param soundfont {BasicSoundBank}
|
|
55
|
-
* @param bankOffset {number} the bank offset for RMIDI
|
|
56
|
-
* @param encoding {string} the encoding of the RMIDI info chunk
|
|
57
|
-
* @param metadata {Partial<RMIDMetadata>} the metadata of the file. Optional. If provided, the encoding is forced to utf-8/
|
|
58
|
-
* @param correctBankOffset {boolean}
|
|
59
|
-
* @returns {IndexedByteArray}
|
|
60
|
-
*/
|
|
61
|
-
export function writeRMIDI(
|
|
62
|
-
soundfontBinary,
|
|
63
|
-
soundfont,
|
|
64
|
-
bankOffset = 0,
|
|
65
|
-
encoding = "Shift_JIS",
|
|
66
|
-
metadata = {},
|
|
67
|
-
correctBankOffset = true
|
|
68
|
-
)
|
|
69
|
-
{
|
|
70
|
-
const mid = this;
|
|
71
|
-
SpessaSynthGroup("%cWriting the RMIDI File...", consoleColors.info);
|
|
72
|
-
SpessaSynthInfo(
|
|
73
|
-
`%cConfiguration: Bank offset: %c${bankOffset}%c, encoding: %c${encoding}`,
|
|
74
|
-
consoleColors.info,
|
|
75
|
-
consoleColors.value,
|
|
76
|
-
consoleColors.info,
|
|
77
|
-
consoleColors.value
|
|
78
|
-
);
|
|
79
|
-
SpessaSynthInfo("metadata", metadata);
|
|
80
|
-
SpessaSynthInfo("Initial bank offset", mid.bankOffset);
|
|
81
|
-
if (correctBankOffset)
|
|
82
|
-
{
|
|
83
|
-
// Add the offset to the bank.
|
|
84
|
-
// See https://github.com/spessasus/sf2-rmidi-specification#readme
|
|
85
|
-
// also fix presets that don't exist
|
|
86
|
-
// since midi player6 doesn't seem to default to 0 when non-existent...
|
|
87
|
-
let system = "gm";
|
|
88
|
-
/**
|
|
89
|
-
* The unwanted system messages such as gm/gm2 on
|
|
90
|
-
* @type {{tNum: number, e: MIDIMessage}[]}
|
|
91
|
-
*/
|
|
92
|
-
let unwantedSystems = [];
|
|
93
|
-
/**
|
|
94
|
-
* indexes for tracks
|
|
95
|
-
* @type {number[]}
|
|
96
|
-
*/
|
|
97
|
-
const eventIndexes = Array(mid.tracks.length).fill(0);
|
|
98
|
-
let remainingTracks = mid.tracks.length;
|
|
99
|
-
|
|
100
|
-
function findFirstEventIndex()
|
|
101
|
-
{
|
|
102
|
-
let index = 0;
|
|
103
|
-
let ticks = Infinity;
|
|
104
|
-
mid.tracks.forEach((track, i) =>
|
|
105
|
-
{
|
|
106
|
-
if (eventIndexes[i] >= track.length)
|
|
107
|
-
{
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (track[eventIndexes[i]].ticks < ticks)
|
|
111
|
-
{
|
|
112
|
-
index = i;
|
|
113
|
-
ticks = track[eventIndexes[i]].ticks;
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
return index;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// it copies midiPorts everywhere else, but here 0 works so DO NOT CHANGE!
|
|
120
|
-
const ports = Array(mid.tracks.length).fill(0);
|
|
121
|
-
const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
|
|
122
|
-
/**
|
|
123
|
-
* @type {{
|
|
124
|
-
* program: number,
|
|
125
|
-
* drums: boolean,
|
|
126
|
-
* lastBank: MIDIMessage,
|
|
127
|
-
* lastBankLSB: MIDIMessage,
|
|
128
|
-
* hasBankSelect: boolean
|
|
129
|
-
* }[]}
|
|
130
|
-
*/
|
|
131
|
-
const channelsInfo = [];
|
|
132
|
-
for (let i = 0; i < channelsAmount; i++)
|
|
133
|
-
{
|
|
134
|
-
channelsInfo.push({
|
|
135
|
-
program: 0,
|
|
136
|
-
drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
|
|
137
|
-
lastBank: undefined,
|
|
138
|
-
lastBankLSB: undefined,
|
|
139
|
-
hasBankSelect: false
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
while (remainingTracks > 0)
|
|
143
|
-
{
|
|
144
|
-
let trackNum = findFirstEventIndex();
|
|
145
|
-
const track = mid.tracks[trackNum];
|
|
146
|
-
if (eventIndexes[trackNum] >= track.length)
|
|
147
|
-
{
|
|
148
|
-
remainingTracks--;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
const e = track[eventIndexes[trackNum]];
|
|
152
|
-
eventIndexes[trackNum]++;
|
|
153
|
-
|
|
154
|
-
let portOffset = mid.midiPortChannelOffsets[ports[trackNum]];
|
|
155
|
-
if (e.messageStatusByte === messageTypes.midiPort)
|
|
156
|
-
{
|
|
157
|
-
ports[trackNum] = e.messageData[0];
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
const status = e.messageStatusByte & 0xF0;
|
|
161
|
-
if (
|
|
162
|
-
status !== messageTypes.controllerChange &&
|
|
163
|
-
status !== messageTypes.programChange &&
|
|
164
|
-
status !== messageTypes.systemExclusive
|
|
165
|
-
)
|
|
166
|
-
{
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (status === messageTypes.systemExclusive)
|
|
171
|
-
{
|
|
172
|
-
// check for drum sysex
|
|
173
|
-
if (!isGSDrumsOn(e))
|
|
174
|
-
{
|
|
175
|
-
// check for XG
|
|
176
|
-
if (isXGOn(e))
|
|
177
|
-
{
|
|
178
|
-
system = "xg";
|
|
179
|
-
}
|
|
180
|
-
else if (isGSOn(e))
|
|
181
|
-
{
|
|
182
|
-
system = "gs";
|
|
183
|
-
}
|
|
184
|
-
else if (isGMOn(e))
|
|
185
|
-
{
|
|
186
|
-
// we do not want gm1
|
|
187
|
-
system = "gm";
|
|
188
|
-
unwantedSystems.push({
|
|
189
|
-
tNum: trackNum,
|
|
190
|
-
e: e
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
else if (isGM2On(e))
|
|
194
|
-
{
|
|
195
|
-
system = "gm2";
|
|
196
|
-
}
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][e.messageData[5] & 0x0F] + portOffset;
|
|
200
|
-
channelsInfo[sysexChannel].drums = !!(e.messageData[7] > 0 && e.messageData[5] >> 4);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// program change
|
|
205
|
-
const chNum = (e.messageStatusByte & 0xF) + portOffset;
|
|
206
|
-
/**
|
|
207
|
-
* @type {{program: number, drums: boolean, lastBank: MIDIMessage, lastBankLSB: MIDIMessage, hasBankSelect: boolean}}
|
|
208
|
-
*/
|
|
209
|
-
const channel = channelsInfo[chNum];
|
|
210
|
-
if (status === messageTypes.programChange)
|
|
211
|
-
{
|
|
212
|
-
const isXG = isSystemXG(system);
|
|
213
|
-
// check if the preset for this program exists
|
|
214
|
-
const initialProgram = e.messageData[0];
|
|
215
|
-
if (channel.drums)
|
|
216
|
-
{
|
|
217
|
-
if (soundfont.presets.findIndex(p => p.program === initialProgram && p.isDrumPreset(
|
|
218
|
-
isXG,
|
|
219
|
-
true
|
|
220
|
-
)) === -1)
|
|
221
|
-
{
|
|
222
|
-
// doesn't exist. pick any preset that has bank 128.
|
|
223
|
-
e.messageData[0] = soundfont.presets.find(p => p.isDrumPreset(isXG))?.program || 0;
|
|
224
|
-
SpessaSynthInfo(
|
|
225
|
-
`%cNo drum preset %c${initialProgram}%c. Channel %c${chNum}%c. Changing program to ${e.messageData[0]}.`,
|
|
226
|
-
consoleColors.info,
|
|
227
|
-
consoleColors.unrecognized,
|
|
228
|
-
consoleColors.info,
|
|
229
|
-
consoleColors.recognized,
|
|
230
|
-
consoleColors.info
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else
|
|
235
|
-
{
|
|
236
|
-
if (soundfont.presets.findIndex(p => p.program === initialProgram && !p.isDrumPreset(isXG)) === -1)
|
|
237
|
-
{
|
|
238
|
-
// doesn't exist. pick any preset that does not have bank 128.
|
|
239
|
-
e.messageData[0] = soundfont.presets.find(p => !p.isDrumPreset(isXG))?.program || 0;
|
|
240
|
-
SpessaSynthInfo(
|
|
241
|
-
`%cNo preset %c${initialProgram}%c. Channel %c${chNum}%c. Changing program to ${e.messageData[0]}.`,
|
|
242
|
-
consoleColors.info,
|
|
243
|
-
consoleColors.unrecognized,
|
|
244
|
-
consoleColors.info,
|
|
245
|
-
consoleColors.recognized,
|
|
246
|
-
consoleColors.info
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
channel.program = e.messageData[0];
|
|
251
|
-
// check if this preset exists for program and bank
|
|
252
|
-
const realBank = Math.max(0, channel.lastBank?.messageData[1] - mid.bankOffset); // make sure to take the previous bank offset into account
|
|
253
|
-
const bankLSB = (channel?.lastBankLSB?.messageData[1] - mid.bankOffset) || 0;
|
|
254
|
-
if (channel.lastBank === undefined)
|
|
255
|
-
{
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
// adjust bank for XG
|
|
259
|
-
let bank = chooseBank(realBank, bankLSB, channel.drums, isXG);
|
|
260
|
-
if (soundfont.presets.findIndex(p => p.bank === bank && p.program === e.messageData[0]) === -1)
|
|
261
|
-
{
|
|
262
|
-
// no preset with this bank. find this program with any bank
|
|
263
|
-
const targetBank = (soundfont.presets.find(p => p.program === e.messageData[0])?.bank + bankOffset) || bankOffset;
|
|
264
|
-
channel.lastBank.messageData[1] = targetBank;
|
|
265
|
-
if (channel?.lastBankLSB?.messageData)
|
|
266
|
-
{
|
|
267
|
-
channel.lastBankLSB.messageData[1] = targetBank;
|
|
268
|
-
}
|
|
269
|
-
SpessaSynthInfo(
|
|
270
|
-
`%cNo preset %c${bank}:${e.messageData[0]}%c. Channel %c${chNum}%c. Changing bank to ${targetBank}.`,
|
|
271
|
-
consoleColors.info,
|
|
272
|
-
consoleColors.unrecognized,
|
|
273
|
-
consoleColors.info,
|
|
274
|
-
consoleColors.recognized,
|
|
275
|
-
consoleColors.info
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
else
|
|
279
|
-
{
|
|
280
|
-
// There is a preset with this bank. Add offset. For drums add the normal offset.
|
|
281
|
-
let drumBank = bank;
|
|
282
|
-
if (isSystemXG(system) && bank === 128)
|
|
283
|
-
{
|
|
284
|
-
bank = 127;
|
|
285
|
-
}
|
|
286
|
-
const newBank = (bank === 128 ? 128 : drumBank) + bankOffset;
|
|
287
|
-
channel.lastBank.messageData[1] = newBank;
|
|
288
|
-
if (channel?.lastBankLSB?.messageData && !channel.drums)
|
|
289
|
-
{
|
|
290
|
-
channel.lastBankLSB.messageData[1] = channel.lastBankLSB.messageData[1] - mid.bankOffset + bankOffset;
|
|
291
|
-
}
|
|
292
|
-
SpessaSynthInfo(
|
|
293
|
-
`%cPreset %c${bank}:${e.messageData[0]}%c exists. Channel %c${chNum}%c. Changing bank to ${newBank}.`,
|
|
294
|
-
consoleColors.info,
|
|
295
|
-
consoleColors.recognized,
|
|
296
|
-
consoleColors.info,
|
|
297
|
-
consoleColors.recognized,
|
|
298
|
-
consoleColors.info
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// controller change
|
|
305
|
-
// we only care about bank-selects
|
|
306
|
-
const isLSB = e.messageData[0] === midiControllers.lsbForControl0BankSelect;
|
|
307
|
-
if (e.messageData[0] !== midiControllers.bankSelect && !isLSB)
|
|
308
|
-
{
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
// bank select
|
|
312
|
-
channel.hasBankSelect = true;
|
|
313
|
-
const bankNumber = e.messageData[1];
|
|
314
|
-
// interpret
|
|
315
|
-
const intepretation = parseBankSelect(
|
|
316
|
-
channel?.lastBank?.messageData[1] || 0,
|
|
317
|
-
bankNumber,
|
|
318
|
-
system,
|
|
319
|
-
isLSB,
|
|
320
|
-
channel.drums,
|
|
321
|
-
chNum
|
|
322
|
-
);
|
|
323
|
-
if (intepretation.drumsStatus === 2)
|
|
324
|
-
{
|
|
325
|
-
channel.drums = true;
|
|
326
|
-
}
|
|
327
|
-
else if (intepretation.drumsStatus === 1)
|
|
328
|
-
{
|
|
329
|
-
channel.drums = false;
|
|
330
|
-
}
|
|
331
|
-
if (isLSB)
|
|
332
|
-
{
|
|
333
|
-
channel.lastBankLSB = e;
|
|
334
|
-
}
|
|
335
|
-
else
|
|
336
|
-
{
|
|
337
|
-
channel.lastBank = e;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// add missing bank selects
|
|
342
|
-
// add all bank selects that are missing for this track
|
|
343
|
-
channelsInfo.forEach((has, ch) =>
|
|
344
|
-
{
|
|
345
|
-
if (has.hasBankSelect === true)
|
|
346
|
-
{
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
// find the first program change (for the given channel)
|
|
350
|
-
const midiChannel = ch % 16;
|
|
351
|
-
const status = messageTypes.programChange | midiChannel;
|
|
352
|
-
// find track with this channel being used
|
|
353
|
-
const portOffset = Math.floor(ch / 16) * 16;
|
|
354
|
-
const port = mid.midiPortChannelOffsets.indexOf(portOffset);
|
|
355
|
-
const track = mid.tracks.find((t, tNum) => mid.midiPorts[tNum] === port && mid.usedChannelsOnTrack[tNum].has(
|
|
356
|
-
midiChannel));
|
|
357
|
-
if (track === undefined)
|
|
358
|
-
{
|
|
359
|
-
// this channel is not used at all
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
let indexToAdd = track.findIndex(e => e.messageStatusByte === status);
|
|
363
|
-
if (indexToAdd === -1)
|
|
364
|
-
{
|
|
365
|
-
// no program change...
|
|
366
|
-
// add programs if they are missing from the track
|
|
367
|
-
// (need them to activate bank 1 for the embedded sfont)
|
|
368
|
-
const programIndex = track.findIndex(e => (e.messageStatusByte > 0x80 && e.messageStatusByte < 0xF0) && (e.messageStatusByte & 0xF) === midiChannel);
|
|
369
|
-
if (programIndex === -1)
|
|
370
|
-
{
|
|
371
|
-
// no voices??? skip
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
const programTicks = track[programIndex].ticks;
|
|
375
|
-
const targetProgram = soundfont.getPreset(0, 0).program;
|
|
376
|
-
track.splice(programIndex, 0, new MIDIMessage(
|
|
377
|
-
programTicks,
|
|
378
|
-
messageTypes.programChange | midiChannel,
|
|
379
|
-
new IndexedByteArray([targetProgram])
|
|
380
|
-
));
|
|
381
|
-
indexToAdd = programIndex;
|
|
382
|
-
}
|
|
383
|
-
SpessaSynthInfo(
|
|
384
|
-
`%cAdding bank select for %c${ch}`,
|
|
385
|
-
consoleColors.info,
|
|
386
|
-
consoleColors.recognized
|
|
387
|
-
);
|
|
388
|
-
const ticks = track[indexToAdd].ticks;
|
|
389
|
-
const targetBank = (soundfont.getPreset(
|
|
390
|
-
0,
|
|
391
|
-
has.program,
|
|
392
|
-
isSystemXG(system)
|
|
393
|
-
)?.bank + bankOffset) || bankOffset;
|
|
394
|
-
track.splice(indexToAdd, 0, new MIDIMessage(
|
|
395
|
-
ticks,
|
|
396
|
-
messageTypes.controllerChange | midiChannel,
|
|
397
|
-
new IndexedByteArray([midiControllers.bankSelect, targetBank])
|
|
398
|
-
));
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// make sure to put xg if gm
|
|
402
|
-
if (system !== "gs" && !isSystemXG(system))
|
|
403
|
-
{
|
|
404
|
-
for (const m of unwantedSystems)
|
|
405
|
-
{
|
|
406
|
-
mid.tracks[m.tNum].splice(mid.tracks[m.tNum].indexOf(m.e), 1);
|
|
407
|
-
}
|
|
408
|
-
let index = 0;
|
|
409
|
-
if (mid.tracks[0][0].messageStatusByte === messageTypes.trackName)
|
|
410
|
-
{
|
|
411
|
-
index++;
|
|
412
|
-
}
|
|
413
|
-
mid.tracks[0].splice(index, 0, getGsOn(0));
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
const newMid = new IndexedByteArray(mid.writeMIDI().buffer);
|
|
417
|
-
|
|
418
|
-
// info data for RMID
|
|
419
|
-
/**
|
|
420
|
-
* @type {Uint8Array[]}
|
|
421
|
-
*/
|
|
422
|
-
const infoContent = [];
|
|
423
|
-
const encoder = new TextEncoder();
|
|
424
|
-
// software (SpessaSynth)
|
|
425
|
-
infoContent.push(
|
|
426
|
-
writeRIFFChunkRaw(RMIDINFOChunks.software, encoder.encode("SpessaSynth"), true)
|
|
427
|
-
);
|
|
428
|
-
// name
|
|
429
|
-
if (metadata.name !== undefined)
|
|
430
|
-
{
|
|
431
|
-
|
|
432
|
-
infoContent.push(
|
|
433
|
-
writeRIFFChunkRaw(RMIDINFOChunks.name, encoder.encode(metadata.name), true)
|
|
434
|
-
);
|
|
435
|
-
encoding = FORCED_ENCODING;
|
|
436
|
-
}
|
|
437
|
-
else
|
|
438
|
-
{
|
|
439
|
-
infoContent.push(
|
|
440
|
-
writeRIFFChunkRaw(RMIDINFOChunks.name, mid.rawMidiName, true)
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
// creation date
|
|
444
|
-
if (metadata.creationDate !== undefined)
|
|
445
|
-
{
|
|
446
|
-
encoding = FORCED_ENCODING;
|
|
447
|
-
infoContent.push(
|
|
448
|
-
writeRIFFChunkRaw(RMIDINFOChunks.creationDate, encoder.encode(metadata.creationDate), true)
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
else
|
|
452
|
-
{
|
|
453
|
-
const today = new Date().toLocaleString(undefined, {
|
|
454
|
-
weekday: "long",
|
|
455
|
-
year: "numeric",
|
|
456
|
-
month: "long",
|
|
457
|
-
day: "numeric",
|
|
458
|
-
hour: "numeric",
|
|
459
|
-
minute: "numeric"
|
|
460
|
-
});
|
|
461
|
-
infoContent.push(
|
|
462
|
-
writeRIFFChunkRaw(RMIDINFOChunks.creationDate, getStringBytes(today, true), true)
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
// comment
|
|
466
|
-
if (metadata.comment !== undefined)
|
|
467
|
-
{
|
|
468
|
-
encoding = FORCED_ENCODING;
|
|
469
|
-
infoContent.push(
|
|
470
|
-
writeRIFFChunkRaw(RMIDINFOChunks.comment, encoder.encode(metadata.comment))
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
// engineer
|
|
474
|
-
if (metadata.engineer !== undefined)
|
|
475
|
-
{
|
|
476
|
-
infoContent.push(
|
|
477
|
-
writeRIFFChunkRaw(RMIDINFOChunks.engineer, encoder.encode(metadata.engineer), true)
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
// album
|
|
481
|
-
if (metadata.album !== undefined)
|
|
482
|
-
{
|
|
483
|
-
// note that there are two album chunks: IPRD and IALB
|
|
484
|
-
encoding = FORCED_ENCODING;
|
|
485
|
-
infoContent.push(
|
|
486
|
-
writeRIFFChunkRaw(RMIDINFOChunks.album, encoder.encode(metadata.album), true)
|
|
487
|
-
);
|
|
488
|
-
infoContent.push(
|
|
489
|
-
writeRIFFChunkRaw(RMIDINFOChunks.album2, encoder.encode(metadata.album), true)
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
// artist
|
|
493
|
-
if (metadata.artist !== undefined)
|
|
494
|
-
{
|
|
495
|
-
encoding = FORCED_ENCODING;
|
|
496
|
-
infoContent.push(
|
|
497
|
-
writeRIFFChunkRaw(RMIDINFOChunks.artist, encoder.encode(metadata.artist), true)
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
// genre
|
|
501
|
-
if (metadata.genre !== undefined)
|
|
502
|
-
{
|
|
503
|
-
encoding = FORCED_ENCODING;
|
|
504
|
-
infoContent.push(
|
|
505
|
-
writeRIFFChunkRaw(RMIDINFOChunks.genre, encoder.encode(metadata.genre), true)
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
// picture
|
|
509
|
-
if (metadata.picture !== undefined)
|
|
510
|
-
{
|
|
511
|
-
infoContent.push(
|
|
512
|
-
writeRIFFChunkRaw(RMIDINFOChunks.picture, new Uint8Array(metadata.picture))
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
// copyright
|
|
516
|
-
if (metadata.copyright !== undefined)
|
|
517
|
-
{
|
|
518
|
-
encoding = FORCED_ENCODING;
|
|
519
|
-
infoContent.push(
|
|
520
|
-
writeRIFFChunkRaw(RMIDINFOChunks.copyright, encoder.encode(metadata.copyright), true)
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
else
|
|
524
|
-
{
|
|
525
|
-
// use midi copyright if possible
|
|
526
|
-
const copyright = mid.copyright.length > 0 ? mid.copyright : DEFAULT_COPYRIGHT;
|
|
527
|
-
infoContent.push(
|
|
528
|
-
writeRIFFChunkRaw(RMIDINFOChunks.copyright, getStringBytes(copyright, true))
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// bank offset
|
|
533
|
-
const DBNK = new IndexedByteArray(2);
|
|
534
|
-
writeLittleEndian(DBNK, bankOffset, 2);
|
|
535
|
-
infoContent.push(writeRIFFChunkRaw(RMIDINFOChunks.bankOffset, DBNK));
|
|
536
|
-
// midi encoding
|
|
537
|
-
if (metadata.midiEncoding !== undefined)
|
|
538
|
-
{
|
|
539
|
-
infoContent.push(
|
|
540
|
-
writeRIFFChunkRaw(RMIDINFOChunks.midiEncoding, encoder.encode(metadata.midiEncoding))
|
|
541
|
-
);
|
|
542
|
-
encoding = FORCED_ENCODING;
|
|
543
|
-
}
|
|
544
|
-
// encoding
|
|
545
|
-
infoContent.push(writeRIFFChunkRaw(RMIDINFOChunks.encoding, getStringBytes(encoding, true)));
|
|
546
|
-
|
|
547
|
-
// combine and write out
|
|
548
|
-
SpessaSynthInfo("%cFinished!", consoleColors.info);
|
|
549
|
-
SpessaSynthGroupEnd();
|
|
550
|
-
return writeRIFFChunkParts(
|
|
551
|
-
"RIFF",
|
|
552
|
-
[
|
|
553
|
-
getStringBytes("RMID"),
|
|
554
|
-
writeRIFFChunkRaw(
|
|
555
|
-
"data",
|
|
556
|
-
newMid
|
|
557
|
-
),
|
|
558
|
-
writeRIFFChunkParts(
|
|
559
|
-
"INFO",
|
|
560
|
-
infoContent,
|
|
561
|
-
true
|
|
562
|
-
),
|
|
563
|
-
soundfontBinary
|
|
564
|
-
]
|
|
565
|
-
);
|
|
566
|
-
}
|