spessasynth_lib 3.24.28 → 3.24.35
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 +1 -1
- package/external_midi/web_midi_link.js +1 -1
- package/index.js +8 -52
- package/midi_parser/basic_midi.js +15 -30
- package/midi_parser/midi_builder.js +6 -6
- package/midi_parser/midi_data.js +2 -2
- package/midi_parser/midi_editor.js +15 -15
- package/midi_parser/midi_loader.js +3 -3
- package/midi_parser/midi_message.js +2 -2
- package/midi_parser/midi_sequence.js +32 -4
- package/midi_parser/midi_writer.js +8 -8
- package/midi_parser/rmidi_writer.js +20 -21
- package/midi_parser/used_keys_loaded.js +8 -6
- package/package.json +1 -1
- package/sequencer/sequencer.js +6 -6
- package/sequencer/worklet_sequencer/events.js +2 -1
- package/sequencer/worklet_sequencer/play.js +2 -3
- package/sequencer/worklet_sequencer/process_event.js +1 -1
- package/sequencer/worklet_sequencer/process_tick.js +1 -1
- package/sequencer/worklet_sequencer/sequencer_message.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +6 -7
- package/sequencer/worklet_sequencer/worklet_sequencer.js +2 -1
- package/soundfont/basic_soundfont/basic_soundfont.js +197 -13
- package/soundfont/basic_soundfont/generator.js +18 -16
- package/soundfont/basic_soundfont/modulator.js +3 -0
- package/soundfont/basic_soundfont/riff_chunk.js +2 -2
- package/soundfont/basic_soundfont/write_dls/art2.js +1 -1
- package/soundfont/basic_soundfont/write_dls/combine_zones.js +12 -10
- package/soundfont/basic_soundfont/write_dls/ins.js +2 -2
- package/soundfont/basic_soundfont/write_dls/lins.js +1 -1
- package/soundfont/basic_soundfont/write_dls/modulator_converter.js +9 -9
- package/soundfont/basic_soundfont/write_dls/rgn2.js +5 -5
- package/soundfont/basic_soundfont/write_dls/wave.js +2 -1
- package/soundfont/basic_soundfont/write_dls/write_dls.js +1 -1
- package/soundfont/basic_soundfont/write_dls/wsmp.js +2 -2
- package/soundfont/basic_soundfont/write_dls/wvpl.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/ibag.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/igen.js +20 -17
- package/soundfont/basic_soundfont/write_sf2/imod.js +3 -3
- package/soundfont/basic_soundfont/write_sf2/inst.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/pbag.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/pgen.js +21 -18
- package/soundfont/basic_soundfont/write_sf2/phdr.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/pmod.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/sdta.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/shdr.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/write.js +7 -6
- package/soundfont/dls/articulator_converter.js +1 -0
- package/soundfont/dls/dls_destinations.js +3 -3
- package/soundfont/dls/dls_preset.js +1 -1
- package/soundfont/dls/dls_soundfont.js +7 -7
- package/soundfont/dls/read_instrument.js +1 -1
- package/soundfont/dls/read_region.js +1 -0
- package/soundfont/dls/read_samples.js +4 -5
- package/soundfont/load_soundfont.js +1 -1
- package/soundfont/read_sf2/presets.js +1 -1
- package/soundfont/read_sf2/soundfont.js +5 -5
- package/synthetizer/audio_effects/effects_config.js +3 -3
- package/synthetizer/audio_effects/fancy_chorus.js +7 -7
- package/synthetizer/audio_effects/reverb.js +5 -5
- package/synthetizer/audio_effects/reverb_as_binary.js +15 -0
- package/synthetizer/audio_effects/reverb_buffer.min.js +1 -0
- package/synthetizer/synth_constants.js +14 -0
- package/synthetizer/synthetizer.js +1 -17
- package/synthetizer/worklet_processor.min.js +14 -12
- package/synthetizer/worklet_system/main_processor.js +2 -2
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +2 -1
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +2 -2
- package/synthetizer/worklet_system/worklet_processor.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +0 -185
- package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/synthetizer/worklet_system/minify_processor.sh +0 -4
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ Supported formats list:
|
|
|
111
111
|
- **First note detection:** Skip unnecessary silence at the start by jumping to the first note!
|
|
112
112
|
- **Lyrics support:** Both regular MIDI and .kar files!
|
|
113
113
|
- **[Write MIDI files from scratch](https://github.com/spessasus/SpessaSynth/wiki/Creating-MIDI-Files)**
|
|
114
|
-
- **Easy saving:** Save with just [one function!](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#
|
|
114
|
+
- **Easy saving:** Save with just [one function!](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#writemidi)
|
|
115
115
|
|
|
116
116
|
#### Read and write [RMID files with embedded SF2 soundfonts](https://github.com/spessasus/sf2-rmidi-specification#readme)
|
|
117
117
|
- **[Level 4](https://github.com/spessasus/sf2-rmidi-specification#level-4) compliance:** Reads and writes *everything!*
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Import modules
|
|
2
2
|
import { loadSoundFont } from "./soundfont/load_soundfont.js";
|
|
3
|
-
import {
|
|
3
|
+
import { BasicSoundBank } from "./soundfont/basic_soundfont/basic_soundfont.js";
|
|
4
4
|
import { BasicSample } from "./soundfont/basic_soundfont/basic_sample.js";
|
|
5
5
|
import { BasicInstrumentZone } from "./soundfont/basic_soundfont/basic_zones.js";
|
|
6
6
|
import { BasicInstrument } from "./soundfont/basic_soundfont/basic_instrument.js";
|
|
@@ -9,40 +9,20 @@ import { Modulator } from "./soundfont/basic_soundfont/modulator.js";
|
|
|
9
9
|
import { BasicPresetZone } from "./soundfont/basic_soundfont/basic_zones.js";
|
|
10
10
|
import { BasicPreset } from "./soundfont/basic_soundfont/basic_preset.js";
|
|
11
11
|
import { BasicMIDI } from "./midi_parser/basic_midi.js";
|
|
12
|
-
import {
|
|
12
|
+
import { MIDIMessage } from "./midi_parser/midi_message.js";
|
|
13
13
|
import { MIDI } from './midi_parser/midi_loader.js';
|
|
14
14
|
import { RMIDINFOChunks } from "./midi_parser/rmidi_writer.js";
|
|
15
|
-
import { MIDIticksToSeconds } from './midi_parser/basic_midi.js';
|
|
16
15
|
import { MIDIBuilder } from "./midi_parser/midi_builder.js";
|
|
17
16
|
import { Synthetizer, VOICE_CAP, DEFAULT_PERCUSSION } from './synthetizer/synthetizer.js';
|
|
18
17
|
import { Sequencer } from './sequencer/sequencer.js';
|
|
19
18
|
import { IndexedByteArray } from './utils/indexed_array.js';
|
|
20
|
-
import { writeMIDIFile } from './midi_parser/midi_writer.js';
|
|
21
|
-
import { writeRMIDI } from './midi_parser/rmidi_writer.js'
|
|
22
|
-
import { applySnapshotToMIDI, modifyMIDI } from './midi_parser/midi_editor.js';
|
|
23
19
|
import { audioBufferToWav } from './utils/buffer_to_wav.js';
|
|
24
|
-
import {
|
|
25
|
-
SpessaSynthInfo,
|
|
26
|
-
SpessaSynthWarn,
|
|
27
|
-
SpessaSynthGroupCollapsed,
|
|
28
|
-
SpessaSynthGroupEnd,
|
|
29
|
-
SpessaSynthTable,
|
|
30
|
-
SpessaSynthLogging,
|
|
31
|
-
SpessaSynthGroup
|
|
32
|
-
} from './utils/loggin.js';
|
|
20
|
+
import { SpessaSynthLogging } from './utils/loggin.js';
|
|
33
21
|
import { midiControllers, messageTypes } from './midi_parser/midi_message.js';
|
|
34
22
|
import { MIDIDeviceHandler} from "./external_midi/midi_handler.js";
|
|
35
|
-
import {
|
|
36
|
-
import { formatTime, formatTitle, consoleColors, arrayToHexString } from './utils/other.js';
|
|
37
|
-
import { readBytesAsUintBigEndian } from './utils/byte_functions/big_endian.js';
|
|
38
|
-
import { readBytesAsString } from "./utils/byte_functions/string.js";
|
|
39
|
-
import { readLittleEndian } from "./utils/byte_functions/little_endian.js";
|
|
40
|
-
import { readVariableLengthQuantity } from "./utils/byte_functions/variable_length_quantity.js";
|
|
23
|
+
import { WebMIDILinkHandler} from "./external_midi/web_midi_link.js";
|
|
41
24
|
import { modulatorSources } from "./soundfont/basic_soundfont/modulator.js";
|
|
42
|
-
import { NON_CC_INDEX_OFFSET } from "./synthetizer/worklet_system/worklet_utilities/controller_tables.js";
|
|
43
|
-
import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from './synthetizer/worklet_system/message_protocol/worklet_message.js';
|
|
44
25
|
import { DEFAULT_SYNTH_CONFIG } from "./synthetizer/audio_effects/effects_config.js";
|
|
45
|
-
import { trimSoundfont} from "./soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js";
|
|
46
26
|
import { WORKLET_URL_ABSOLUTE } from './synthetizer/worklet_url.js';
|
|
47
27
|
import { SynthesizerSnapshot} from "./synthetizer/worklet_system/snapshot/synthesizer_snapshot.js";
|
|
48
28
|
import { ChannelSnapshot } from "./synthetizer/worklet_system/snapshot/channel_snapshot.js";
|
|
@@ -59,7 +39,7 @@ export {
|
|
|
59
39
|
DEFAULT_SYNTH_CONFIG,
|
|
60
40
|
|
|
61
41
|
// SoundFont
|
|
62
|
-
|
|
42
|
+
BasicSoundBank,
|
|
63
43
|
BasicSample,
|
|
64
44
|
BasicInstrumentZone,
|
|
65
45
|
BasicInstrument,
|
|
@@ -68,46 +48,22 @@ export {
|
|
|
68
48
|
Generator,
|
|
69
49
|
Modulator,
|
|
70
50
|
loadSoundFont,
|
|
71
|
-
trimSoundfont,
|
|
72
51
|
modulatorSources,
|
|
73
52
|
|
|
74
53
|
// MIDI
|
|
75
54
|
MIDI,
|
|
76
55
|
BasicMIDI,
|
|
77
56
|
MIDIBuilder,
|
|
78
|
-
|
|
79
|
-
MidiMessage,
|
|
80
|
-
writeMIDIFile,
|
|
81
|
-
writeRMIDI,
|
|
82
|
-
applySnapshotToMIDI,
|
|
83
|
-
modifyMIDI,
|
|
84
|
-
MIDIticksToSeconds,
|
|
57
|
+
MIDIMessage,
|
|
85
58
|
RMIDINFOChunks,
|
|
86
59
|
|
|
87
60
|
// Utilities
|
|
61
|
+
IndexedByteArray,
|
|
88
62
|
audioBufferToWav,
|
|
89
63
|
SpessaSynthLogging,
|
|
90
|
-
SpessaSynthGroup,
|
|
91
|
-
SpessaSynthTable,
|
|
92
|
-
SpessaSynthGroupEnd,
|
|
93
|
-
SpessaSynthInfo,
|
|
94
|
-
SpessaSynthWarn,
|
|
95
|
-
SpessaSynthGroupCollapsed,
|
|
96
64
|
midiControllers,
|
|
97
65
|
messageTypes,
|
|
98
66
|
MIDIDeviceHandler,
|
|
99
|
-
|
|
100
|
-
arrayToHexString,
|
|
101
|
-
consoleColors,
|
|
102
|
-
formatTitle,
|
|
103
|
-
formatTime,
|
|
104
|
-
|
|
105
|
-
readBytesAsUintBigEndian,
|
|
106
|
-
readBytesAsString,
|
|
107
|
-
readLittleEndian,
|
|
108
|
-
readVariableLengthQuantity,
|
|
109
|
-
|
|
110
|
-
NON_CC_INDEX_OFFSET,
|
|
111
|
-
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
67
|
+
WebMIDILinkHandler,
|
|
112
68
|
WORKLET_URL_ABSOLUTE
|
|
113
69
|
};
|
|
@@ -4,13 +4,17 @@ import { messageTypes } from "./midi_message.js";
|
|
|
4
4
|
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
5
5
|
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../utils/loggin.js";
|
|
6
6
|
import { consoleColors, formatTitle, sanitizeKarLyrics } from "../utils/other.js";
|
|
7
|
+
import { writeMIDI } from "./midi_writer.js";
|
|
8
|
+
import { applySnapshotToMIDI, modifyMIDI } from "./midi_editor.js";
|
|
9
|
+
import { writeRMIDI } from "./rmidi_writer.js";
|
|
10
|
+
import { getUsedProgramsAndKeys } from "./used_keys_loaded.js";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* BasicMIDI is the base of a complete MIDI file, used by the sequencer internally.
|
|
10
14
|
* BasicMIDI is not available on the main thread, as it contains the actual track data which can be large.
|
|
11
15
|
* It can be accessed by calling getMIDI() on the Sequencer.
|
|
12
16
|
*/
|
|
13
|
-
|
|
17
|
+
class BasicMIDI extends MIDISequenceData
|
|
14
18
|
{
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -21,8 +25,8 @@ export class BasicMIDI extends MIDISequenceData
|
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* The actual track data of the MIDI file, represented as an array of tracks.
|
|
24
|
-
* Tracks are arrays of
|
|
25
|
-
* @type {
|
|
28
|
+
* Tracks are arrays of MIDIMessage objects.
|
|
29
|
+
* @type {MIDIMessage[][]}
|
|
26
30
|
*/
|
|
27
31
|
tracks = [];
|
|
28
32
|
|
|
@@ -560,7 +564,7 @@ export class BasicMIDI extends MIDISequenceData
|
|
|
560
564
|
* The total playback time, in seconds
|
|
561
565
|
* @type {number}
|
|
562
566
|
*/
|
|
563
|
-
this.duration = MIDIticksToSeconds(this.lastVoiceEventTick
|
|
567
|
+
this.duration = this.MIDIticksToSeconds(this.lastVoiceEventTick);
|
|
564
568
|
|
|
565
569
|
SpessaSynthInfo("%cSuccess!", consoleColors.recognized);
|
|
566
570
|
SpessaSynthGroupEnd();
|
|
@@ -581,29 +585,10 @@ export class BasicMIDI extends MIDISequenceData
|
|
|
581
585
|
}
|
|
582
586
|
}
|
|
583
587
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
export
|
|
591
|
-
{
|
|
592
|
-
let totalSeconds = 0;
|
|
593
|
-
|
|
594
|
-
while (ticks > 0)
|
|
595
|
-
{
|
|
596
|
-
// tempo changes are reversed, so the first element is the last tempo change
|
|
597
|
-
// and the last element is the first tempo change
|
|
598
|
-
// (always at tick 0 and tempo 120)
|
|
599
|
-
// find the last tempo change that has occurred
|
|
600
|
-
let tempo = mid.tempoChanges.find(v => v.ticks < ticks);
|
|
601
|
-
|
|
602
|
-
// calculate the difference and tempo time
|
|
603
|
-
let timeSinceLastTempo = ticks - tempo.ticks;
|
|
604
|
-
totalSeconds += (timeSinceLastTempo * 60) / (tempo.tempo * mid.timeDivision);
|
|
605
|
-
ticks -= timeSinceLastTempo;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
return totalSeconds;
|
|
609
|
-
}
|
|
588
|
+
BasicMIDI.prototype.writeMIDI = writeMIDI;
|
|
589
|
+
BasicMIDI.prototype.modifyMIDI = modifyMIDI;
|
|
590
|
+
BasicMIDI.prototype.applySnapshotToMIDI = applySnapshotToMIDI;
|
|
591
|
+
BasicMIDI.prototype.writeRMIDI = writeRMIDI;
|
|
592
|
+
BasicMIDI.prototype.getUsedProgramsAndKeys = getUsedProgramsAndKeys;
|
|
593
|
+
|
|
594
|
+
export { BasicMIDI };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BasicMIDI } from "./basic_midi.js";
|
|
2
|
-
import { messageTypes,
|
|
2
|
+
import { messageTypes, MIDIMessage } from "./midi_message.js";
|
|
3
3
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
4
4
|
import { SpessaSynthWarn } from "../utils/loggin.js";
|
|
5
5
|
|
|
@@ -59,7 +59,7 @@ export class MIDIBuilder extends BasicMIDI
|
|
|
59
59
|
}
|
|
60
60
|
this.tracks.push([]);
|
|
61
61
|
this.tracks[this.tracksAmount - 1].push(
|
|
62
|
-
new
|
|
62
|
+
new MIDIMessage(0, messageTypes.endOfTrack, new IndexedByteArray(0))
|
|
63
63
|
);
|
|
64
64
|
this.addEvent(0, this.tracksAmount - 1, messageTypes.trackName, this.encoder.encode(name));
|
|
65
65
|
this.addEvent(0, this.tracksAmount - 1, messageTypes.midiPort, [port]);
|
|
@@ -83,15 +83,15 @@ export class MIDIBuilder extends BasicMIDI
|
|
|
83
83
|
SpessaSynthWarn("The EndOfTrack is added automatically. Ignoring!");
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
-
// remove end of track
|
|
86
|
+
// remove the end of track
|
|
87
87
|
this.tracks[track].pop();
|
|
88
|
-
this.tracks[track].push(new
|
|
88
|
+
this.tracks[track].push(new MIDIMessage(
|
|
89
89
|
ticks,
|
|
90
90
|
event,
|
|
91
91
|
new IndexedByteArray(eventData)
|
|
92
92
|
));
|
|
93
|
-
// add end of track
|
|
94
|
-
this.tracks[track].push(new
|
|
93
|
+
// add the end of track
|
|
94
|
+
this.tracks[track].push(new MIDIMessage(
|
|
95
95
|
ticks,
|
|
96
96
|
messageTypes.endOfTrack,
|
|
97
97
|
new IndexedByteArray(0)
|
package/midi_parser/midi_data.js
CHANGED
|
@@ -5,7 +5,7 @@ import { MIDISequenceData } from "./midi_sequence.js";
|
|
|
5
5
|
* Use getMIDI() to get the actual sequence.
|
|
6
6
|
* This class contains all properties that MIDI does, except for tracks and the embedded soundfont.
|
|
7
7
|
*/
|
|
8
|
-
export class
|
|
8
|
+
export class MIDIData extends MIDISequenceData
|
|
9
9
|
{
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -53,7 +53,7 @@ export class MidiData extends MIDISequenceData
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Temporary MIDI data used when the MIDI is not loaded.
|
|
56
|
-
* @type {
|
|
56
|
+
* @type {MIDIData}
|
|
57
57
|
*/
|
|
58
58
|
export const DUMMY_MIDI_DATA = {
|
|
59
59
|
duration: 99999,
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { messageTypes, midiControllers,
|
|
1
|
+
import { messageTypes, midiControllers, MIDIMessage } from "./midi_message.js";
|
|
2
2
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
3
3
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../utils/loggin.js";
|
|
4
4
|
import { consoleColors } from "../utils/other.js";
|
|
5
|
-
import { DEFAULT_PERCUSSION } from "../synthetizer/synthetizer.js";
|
|
6
5
|
|
|
7
6
|
import { customControllers } from "../synthetizer/worklet_system/worklet_utilities/controller_tables.js";
|
|
7
|
+
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param ticks {number}
|
|
11
|
-
* @returns {
|
|
11
|
+
* @returns {MIDIMessage}
|
|
12
12
|
*/
|
|
13
13
|
export function getGsOn(ticks)
|
|
14
14
|
{
|
|
15
|
-
return new
|
|
15
|
+
return new MIDIMessage(
|
|
16
16
|
ticks,
|
|
17
17
|
messageTypes.systemExclusive,
|
|
18
18
|
new IndexedByteArray([
|
|
@@ -35,11 +35,11 @@ export function getGsOn(ticks)
|
|
|
35
35
|
* @param cc {number}
|
|
36
36
|
* @param value {number}
|
|
37
37
|
* @param ticks {number}
|
|
38
|
-
* @returns {
|
|
38
|
+
* @returns {MIDIMessage}
|
|
39
39
|
*/
|
|
40
40
|
function getControllerChange(channel, cc, value, ticks)
|
|
41
41
|
{
|
|
42
|
-
return new
|
|
42
|
+
return new MIDIMessage(
|
|
43
43
|
ticks,
|
|
44
44
|
messageTypes.controllerChange | (channel % 16),
|
|
45
45
|
new IndexedByteArray([cc, value])
|
|
@@ -49,7 +49,7 @@ function getControllerChange(channel, cc, value, ticks)
|
|
|
49
49
|
/**
|
|
50
50
|
* @param channel {number}
|
|
51
51
|
* @param ticks {number}
|
|
52
|
-
* @returns {
|
|
52
|
+
* @returns {MIDIMessage}
|
|
53
53
|
*/
|
|
54
54
|
function getDrumChange(channel, ticks)
|
|
55
55
|
{
|
|
@@ -70,7 +70,7 @@ function getDrumChange(channel, ticks)
|
|
|
70
70
|
const sum = 0x40 + chanAddress + 0x15 + 0x01;
|
|
71
71
|
const checksum = 128 - (sum % 128);
|
|
72
72
|
// add system exclusive to enable drums
|
|
73
|
-
return new
|
|
73
|
+
return new MIDIMessage(
|
|
74
74
|
ticks,
|
|
75
75
|
messageTypes.systemExclusive,
|
|
76
76
|
new IndexedByteArray([
|
|
@@ -110,20 +110,20 @@ function getDrumChange(channel, ticks)
|
|
|
110
110
|
* Allows easy editing of the file by removing channels, changing programs,
|
|
111
111
|
* changing controllers and transposing channels. Note that this modifies the MIDI in-place.
|
|
112
112
|
*
|
|
113
|
-
* @
|
|
113
|
+
* @this {BasicMIDI}
|
|
114
114
|
* @param {DesiredProgramChange[]} desiredProgramChanges - The programs to set on given channels.
|
|
115
115
|
* @param {DesiredControllerChange[]} desiredControllerChanges - The controllers to set on given channels.
|
|
116
116
|
* @param {number[]} desiredChannelsToClear - The channels to remove from the sequence.
|
|
117
117
|
* @param {DesiredChanneltranspose[]} desiredChannelsToTranspose - The channels to transpose.
|
|
118
118
|
*/
|
|
119
119
|
export function modifyMIDI(
|
|
120
|
-
midi,
|
|
121
120
|
desiredProgramChanges = [],
|
|
122
121
|
desiredControllerChanges = [],
|
|
123
122
|
desiredChannelsToClear = [],
|
|
124
123
|
desiredChannelsToTranspose = []
|
|
125
124
|
)
|
|
126
125
|
{
|
|
126
|
+
const midi = this;
|
|
127
127
|
SpessaSynthGroupCollapsed("%cApplying changes to the MIDI file...", consoleColors.info);
|
|
128
128
|
|
|
129
129
|
/**
|
|
@@ -252,7 +252,7 @@ export function modifyMIDI(
|
|
|
252
252
|
};
|
|
253
253
|
|
|
254
254
|
/**
|
|
255
|
-
* @param e {
|
|
255
|
+
* @param e {MIDIMessage}
|
|
256
256
|
* @param offset{number}
|
|
257
257
|
*/
|
|
258
258
|
const addEventBefore = (e, offset = 0) =>
|
|
@@ -352,7 +352,7 @@ export function modifyMIDI(
|
|
|
352
352
|
// the output event order is: drums -> lsb -> msb -> program change
|
|
353
353
|
|
|
354
354
|
// add program change
|
|
355
|
-
const programChange = new
|
|
355
|
+
const programChange = new MIDIMessage(
|
|
356
356
|
e.ticks,
|
|
357
357
|
messageTypes.programChange | midiChannel,
|
|
358
358
|
new IndexedByteArray([
|
|
@@ -527,10 +527,10 @@ export function modifyMIDI(
|
|
|
527
527
|
|
|
528
528
|
/**
|
|
529
529
|
* Modifies the sequence according to the locked presets and controllers in the given snapshot
|
|
530
|
-
* @
|
|
530
|
+
* @this {BasicMIDI}
|
|
531
531
|
* @param snapshot {SynthesizerSnapshot}
|
|
532
532
|
*/
|
|
533
|
-
export function applySnapshotToMIDI(
|
|
533
|
+
export function applySnapshotToMIDI(snapshot)
|
|
534
534
|
{
|
|
535
535
|
/**
|
|
536
536
|
* @type {{
|
|
@@ -600,5 +600,5 @@ export function applySnapshotToMIDI(midi, snapshot)
|
|
|
600
600
|
});
|
|
601
601
|
});
|
|
602
602
|
});
|
|
603
|
-
modifyMIDI(
|
|
603
|
+
this.modifyMIDI(programChanges, controllerChanges, channelsToClear, channelsToTranspose);
|
|
604
604
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dataBytesAmount, getChannel,
|
|
1
|
+
import { dataBytesAmount, getChannel, MIDIMessage } from "./midi_message.js";
|
|
2
2
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
3
3
|
import { consoleColors } from "../utils/other.js";
|
|
4
4
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
|
|
@@ -172,7 +172,7 @@ class MIDI extends BasicMIDI
|
|
|
172
172
|
for (let i = 0; i < this.tracksAmount; i++)
|
|
173
173
|
{
|
|
174
174
|
/**
|
|
175
|
-
* @type {
|
|
175
|
+
* @type {MIDIMessage[]}
|
|
176
176
|
*/
|
|
177
177
|
const track = [];
|
|
178
178
|
const trackChunk = this.readMIDIChunk(fileByteArray);
|
|
@@ -262,7 +262,7 @@ class MIDI extends BasicMIDI
|
|
|
262
262
|
trackChunk.data.currentIndex,
|
|
263
263
|
trackChunk.data.currentIndex + eventDataLength
|
|
264
264
|
), 0);
|
|
265
|
-
const event = new
|
|
265
|
+
const event = new MIDIMessage(totalTicks, statusByte, eventData);
|
|
266
266
|
track.push(event);
|
|
267
267
|
// advance the track chunk
|
|
268
268
|
trackChunk.data.currentIndex += eventDataLength;
|
|
@@ -5,7 +5,7 @@ import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
|
5
5
|
* purpose: contains enums for midi events and controllers and functions to parse them
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export class
|
|
8
|
+
export class MIDIMessage
|
|
9
9
|
{
|
|
10
10
|
/**
|
|
11
11
|
* Absolute number of MIDI ticks from the start of the track.
|
|
@@ -249,6 +249,6 @@ export const dataBytesAmount = {
|
|
|
249
249
|
0xA: 2, // note at
|
|
250
250
|
0xB: 2, // cc change
|
|
251
251
|
0xC: 1, // pg change
|
|
252
|
-
0xD: 1, // channel
|
|
252
|
+
0xD: 1, // channel after touch
|
|
253
253
|
0xE: 2 // pitch wheel
|
|
254
254
|
};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* This is the base type for MIDI files. It contains all the "metadata" and information.
|
|
3
3
|
* It extends to:
|
|
4
4
|
* - BasicMIDI, which contains the actual track data of the MIDI file. Essentially the MIDI file itself.
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
5
|
+
* - MIDIData, which contains all properties that MIDI does, except for tracks and the embedded soundfont.
|
|
6
|
+
* MIDIData is the "shell" of the file which is available on the main thread at all times, containing the metadata.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
class MIDISequenceData
|
|
9
9
|
{
|
|
10
10
|
/**
|
|
11
11
|
* The time division of the sequence, representing the number of ticks per beat.
|
|
@@ -143,4 +143,32 @@ export class MIDISequenceData
|
|
|
143
143
|
* @type {boolean}
|
|
144
144
|
*/
|
|
145
145
|
isKaraokeFile = false;
|
|
146
|
-
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Converts ticks to time in seconds
|
|
149
|
+
* @param ticks {number} time in MIDI ticks
|
|
150
|
+
* @returns {number} time in seconds
|
|
151
|
+
*/
|
|
152
|
+
MIDIticksToSeconds(ticks)
|
|
153
|
+
{
|
|
154
|
+
let totalSeconds = 0;
|
|
155
|
+
|
|
156
|
+
while (ticks > 0)
|
|
157
|
+
{
|
|
158
|
+
// tempo changes are reversed, so the first element is the last tempo change
|
|
159
|
+
// and the last element is the first tempo change
|
|
160
|
+
// (always at tick 0 and tempo 120)
|
|
161
|
+
// find the last tempo change that has occurred
|
|
162
|
+
let tempo = this.tempoChanges.find(v => v.ticks < ticks);
|
|
163
|
+
|
|
164
|
+
// calculate the difference and tempo time
|
|
165
|
+
let timeSinceLastTempo = ticks - tempo.ticks;
|
|
166
|
+
totalSeconds += (timeSinceLastTempo * 60) / (tempo.tempo * this.timeDivision);
|
|
167
|
+
ticks -= timeSinceLastTempo;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return totalSeconds;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { MIDISequenceData };
|
|
@@ -3,12 +3,12 @@ import { writeVariableLengthQuantity } from "../utils/byte_functions/variable_le
|
|
|
3
3
|
import { writeBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Exports the midi as a
|
|
7
|
-
* @
|
|
8
|
-
* @returns {Uint8Array} the binary .mid file data
|
|
6
|
+
* Exports the midi as a standard MIDI file
|
|
7
|
+
* @this {BasicMIDI}
|
|
9
8
|
*/
|
|
10
|
-
export function
|
|
9
|
+
export function writeMIDI()
|
|
11
10
|
{
|
|
11
|
+
const midi = this;
|
|
12
12
|
if (!midi.tracks)
|
|
13
13
|
{
|
|
14
14
|
throw new Error("MIDI has no tracks!");
|
|
@@ -24,7 +24,7 @@ export function writeMIDIFile(midi)
|
|
|
24
24
|
let runningByte = undefined;
|
|
25
25
|
for (const event of track)
|
|
26
26
|
{
|
|
27
|
-
//
|
|
27
|
+
// Ticks stored in MIDI are absolute, but SMF wants relative. Convert them here.
|
|
28
28
|
const deltaTicks = event.ticks - currentTick;
|
|
29
29
|
/**
|
|
30
30
|
* @type {number[]}
|
|
@@ -33,7 +33,7 @@ export function writeMIDIFile(midi)
|
|
|
33
33
|
// determine the message
|
|
34
34
|
if (event.messageStatusByte <= messageTypes.keySignature || event.messageStatusByte === messageTypes.sequenceSpecific)
|
|
35
35
|
{
|
|
36
|
-
// this is a meta
|
|
36
|
+
// this is a meta-message
|
|
37
37
|
// syntax is FF<type><length><data>
|
|
38
38
|
messageData = [0xff, event.messageStatusByte, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
|
|
39
39
|
}
|
|
@@ -49,7 +49,7 @@ export function writeMIDIFile(midi)
|
|
|
49
49
|
messageData = [];
|
|
50
50
|
if (runningByte !== event.messageStatusByte)
|
|
51
51
|
{
|
|
52
|
-
//
|
|
52
|
+
// Running byte was not the byte we want. Add the byte here.
|
|
53
53
|
runningByte = event.messageStatusByte;
|
|
54
54
|
// add the status byte to the midi
|
|
55
55
|
messageData.push(event.messageStatusByte);
|
|
@@ -59,7 +59,7 @@ export function writeMIDIFile(midi)
|
|
|
59
59
|
}
|
|
60
60
|
// write VLQ
|
|
61
61
|
binaryTrack.push(...writeVariableLengthQuantity(deltaTicks));
|
|
62
|
-
// write message
|
|
62
|
+
// write the message
|
|
63
63
|
binaryTrack.push(...messageData);
|
|
64
64
|
currentTick += deltaTicks;
|
|
65
65
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { combineArrays, IndexedByteArray } from "../utils/indexed_array.js";
|
|
2
|
-
import { writeMIDIFile } from "./midi_writer.js";
|
|
3
2
|
import { writeRIFFOddSize } from "../soundfont/basic_soundfont/riff_chunk.js";
|
|
4
3
|
import { getStringBytes, getStringBytesZero } from "../utils/byte_functions/string.js";
|
|
5
|
-
import { messageTypes, midiControllers,
|
|
6
|
-
import { DEFAULT_PERCUSSION } from "../synthetizer/synthetizer.js";
|
|
4
|
+
import { messageTypes, midiControllers, MIDIMessage } from "./midi_message.js";
|
|
7
5
|
import { getGsOn } from "./midi_editor.js";
|
|
8
6
|
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../utils/loggin.js";
|
|
9
7
|
import { consoleColors } from "../utils/other.js";
|
|
10
8
|
import { writeLittleEndian } from "../utils/byte_functions/little_endian.js";
|
|
9
|
+
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* @enum {string}
|
|
@@ -48,9 +47,9 @@ const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
|
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
49
|
* Writes an RMIDI file
|
|
50
|
+
* @this {BasicMIDI}
|
|
51
51
|
* @param soundfontBinary {Uint8Array}
|
|
52
|
-
* @param
|
|
53
|
-
* @param soundfont {BasicSoundFont}
|
|
52
|
+
* @param soundfont {BasicSoundBank}
|
|
54
53
|
* @param bankOffset {number} the bank offset for RMIDI
|
|
55
54
|
* @param encoding {string} the encoding of the RMIDI info chunk
|
|
56
55
|
* @param metadata {RMIDMetadata} the metadata of the file. Optional. If provided, the encoding is forced to utf-8/
|
|
@@ -59,7 +58,6 @@ const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
|
|
|
59
58
|
*/
|
|
60
59
|
export function writeRMIDI(
|
|
61
60
|
soundfontBinary,
|
|
62
|
-
mid,
|
|
63
61
|
soundfont,
|
|
64
62
|
bankOffset = 0,
|
|
65
63
|
encoding = "Shift_JIS",
|
|
@@ -67,6 +65,7 @@ export function writeRMIDI(
|
|
|
67
65
|
correctBankOffset = true
|
|
68
66
|
)
|
|
69
67
|
{
|
|
68
|
+
const mid = this;
|
|
70
69
|
SpessaSynthGroup("%cWriting the RMIDI File...", consoleColors.info);
|
|
71
70
|
SpessaSynthInfo(
|
|
72
71
|
`%cConfiguration: Bank offset: %c${bankOffset}%c, encoding: %c${encoding}`,
|
|
@@ -79,14 +78,14 @@ export function writeRMIDI(
|
|
|
79
78
|
SpessaSynthInfo("Initial bank offset", mid.bankOffset);
|
|
80
79
|
if (correctBankOffset)
|
|
81
80
|
{
|
|
82
|
-
//
|
|
81
|
+
// Add the offset to the bank.
|
|
83
82
|
// See https://github.com/spessasus/sf2-rmidi-specification#readme
|
|
84
|
-
// also fix presets that don't
|
|
85
|
-
// since
|
|
83
|
+
// also fix presets that don't exist
|
|
84
|
+
// since midi player6 doesn't seem to default to 0 when non-existent...
|
|
86
85
|
let system = "gm";
|
|
87
86
|
/**
|
|
88
87
|
* The unwanted system messages such as gm/gm2 on
|
|
89
|
-
* @type {{tNum: number, e:
|
|
88
|
+
* @type {{tNum: number, e: MIDIMessage}[]}
|
|
90
89
|
*/
|
|
91
90
|
let unwantedSystems = [];
|
|
92
91
|
/**
|
|
@@ -115,14 +114,14 @@ export function writeRMIDI(
|
|
|
115
114
|
return index;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
// it copies midiPorts everywhere else, but here 0 works so DO NOT CHANGE
|
|
117
|
+
// it copies midiPorts everywhere else, but here 0 works so DO NOT CHANGE!
|
|
119
118
|
const ports = Array(mid.tracks.length).fill(0);
|
|
120
119
|
const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
|
|
121
120
|
/**
|
|
122
121
|
* @type {{
|
|
123
122
|
* program: number,
|
|
124
123
|
* drums: boolean,
|
|
125
|
-
* lastBank:
|
|
124
|
+
* lastBank: MIDIMessage,
|
|
126
125
|
* hasBankSelect: boolean
|
|
127
126
|
* }[]}
|
|
128
127
|
*/
|
|
@@ -222,7 +221,7 @@ export function writeRMIDI(
|
|
|
222
221
|
{
|
|
223
222
|
if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank === 128) === -1)
|
|
224
223
|
{
|
|
225
|
-
// doesn't exist. pick any preset that has
|
|
224
|
+
// doesn't exist. pick any preset that has bank 128.
|
|
226
225
|
e.messageData[0] = soundfont.presets.find(p => p.bank === 128)?.program || 0;
|
|
227
226
|
}
|
|
228
227
|
}
|
|
@@ -230,7 +229,7 @@ export function writeRMIDI(
|
|
|
230
229
|
{
|
|
231
230
|
if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank !== 128) === -1)
|
|
232
231
|
{
|
|
233
|
-
// doesn't exist. pick any preset that does not have
|
|
232
|
+
// doesn't exist. pick any preset that does not have bank 128.
|
|
234
233
|
e.messageData[0] = soundfont.presets.find(p => p.bank !== 128)?.program || 0;
|
|
235
234
|
}
|
|
236
235
|
}
|
|
@@ -244,7 +243,7 @@ export function writeRMIDI(
|
|
|
244
243
|
}
|
|
245
244
|
if (system === "xg" && channel.drums)
|
|
246
245
|
{
|
|
247
|
-
// drums override: set bank to 127
|
|
246
|
+
// drums override: set the bank to 127
|
|
248
247
|
channelsInfo[chNum].lastBank.messageData[1] = 127;
|
|
249
248
|
}
|
|
250
249
|
|
|
@@ -262,7 +261,7 @@ export function writeRMIDI(
|
|
|
262
261
|
}
|
|
263
262
|
else
|
|
264
263
|
{
|
|
265
|
-
//
|
|
264
|
+
// There is a preset with this bank. Add offset. For drums add the normal offset.
|
|
266
265
|
let drumBank = system === "xg" ? 127 : 0;
|
|
267
266
|
const newBank = (bank === 128 ? drumBank : realBank) + bankOffset;
|
|
268
267
|
channel.lastBank.messageData[1] = newBank;
|
|
@@ -298,7 +297,7 @@ export function writeRMIDI(
|
|
|
298
297
|
{
|
|
299
298
|
return;
|
|
300
299
|
}
|
|
301
|
-
// find first program change (for the given channel)
|
|
300
|
+
// find the first program change (for the given channel)
|
|
302
301
|
const midiChannel = ch % 16;
|
|
303
302
|
const status = messageTypes.programChange | midiChannel;
|
|
304
303
|
// find track with this channel being used
|
|
@@ -325,7 +324,7 @@ export function writeRMIDI(
|
|
|
325
324
|
}
|
|
326
325
|
const programTicks = track[programIndex].ticks;
|
|
327
326
|
const targetProgram = soundfont.getPreset(0, 0).program;
|
|
328
|
-
track.splice(programIndex, 0, new
|
|
327
|
+
track.splice(programIndex, 0, new MIDIMessage(
|
|
329
328
|
programTicks,
|
|
330
329
|
messageTypes.programChange | midiChannel,
|
|
331
330
|
new IndexedByteArray([targetProgram])
|
|
@@ -342,7 +341,7 @@ export function writeRMIDI(
|
|
|
342
341
|
0,
|
|
343
342
|
has.program
|
|
344
343
|
)?.bank + bankOffset) || bankOffset;
|
|
345
|
-
track.splice(indexToAdd, 0, new
|
|
344
|
+
track.splice(indexToAdd, 0, new MIDIMessage(
|
|
346
345
|
ticks,
|
|
347
346
|
messageTypes.controllerChange | midiChannel,
|
|
348
347
|
new IndexedByteArray([midiControllers.bankSelect, targetBank])
|
|
@@ -364,9 +363,9 @@ export function writeRMIDI(
|
|
|
364
363
|
mid.tracks[0].splice(index, 0, getGsOn(0));
|
|
365
364
|
}
|
|
366
365
|
}
|
|
367
|
-
const newMid = new IndexedByteArray(
|
|
366
|
+
const newMid = new IndexedByteArray(mid.writeMIDI().buffer);
|
|
368
367
|
|
|
369
|
-
//
|
|
368
|
+
// info data for RMID
|
|
370
369
|
/**
|
|
371
370
|
* @type {Uint8Array[]}
|
|
372
371
|
*/
|