spessasynth_lib 3.25.21 → 3.25.23
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/index.js +7 -7
- package/{midi_parser → midi}/basic_midi.js +6 -4
- package/{midi_parser → midi}/midi_loader.js +1 -1
- package/midi/midi_tools/get_note_times.js +154 -0
- package/{midi_parser → midi/midi_tools}/midi_editor.js +8 -8
- package/{midi_parser → midi/midi_tools}/midi_writer.js +3 -3
- package/{midi_parser → midi/midi_tools}/rmidi_writer.js +10 -10
- package/{midi_parser → midi/midi_tools}/used_keys_loaded.js +7 -7
- package/{midi_parser → midi}/xmf_loader.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer_engine/events.js +3 -106
- package/sequencer/sequencer_engine/play.js +3 -5
- package/sequencer/sequencer_engine/process_event.js +1 -1
- package/sequencer/sequencer_engine/sequencer_engine.js +1 -3
- package/sequencer/sequencer_engine/song_control.js +3 -3
- package/sequencer/worklet_wrapper/sequencer.js +3 -3
- package/soundfont/basic_soundfont/modulator.js +1 -1
- package/soundfont/basic_soundfont/write_dls/modulator_converter.js +1 -1
- package/soundfont/dls/articulator_converter.js +1 -1
- package/synthetizer/audio_engine/README.md +5 -5
- package/synthetizer/audio_engine/{worklet_utilities/worklet_modulator.js → engine_components/compute_modulator.js} +12 -12
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/controller_tables.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods/worklet_key_modifier.js → engine_components/key_modifier_manager.js} +24 -32
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/lowpass_filter.js +1 -1
- package/synthetizer/audio_engine/{worklet_utilities/worklet_processor_channel.js → engine_components/midi_audio_channel.js} +43 -43
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/modulation_envelope.js +6 -6
- package/synthetizer/audio_engine/{worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js → engine_components/soundfont_manager.js} +3 -29
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/stereo_panner.js +3 -3
- package/synthetizer/audio_engine/{worklet_utilities/worklet_voice.js → engine_components/voice.js} +46 -40
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/volume_envelope.js +17 -17
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/wavetable_oscillator.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/controller_control/controller_change.js +4 -4
- package/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/controller_control/reset_controllers.js +7 -7
- package/synthetizer/audio_engine/{worklet_methods/create_worklet_channel.js → engine_methods/create_midi_channel.js} +4 -4
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/data_entry/data_entry_coarse.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/data_entry/data_entry_fine.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/mute_channel.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/note_on.js +13 -6
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/program_change.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/render_voice.js +13 -13
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/kill_note.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/note_off.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/stop_all_notes.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/voice_killing.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/system_exclusive.js +37 -4
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/channel_pressure.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/pitch_wheel.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/poly_pressure.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_master_tuning.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_modulation_depth.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_octave_tuning.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_tuning.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/transpose_channel.js +3 -3
- package/synthetizer/audio_engine/main_processor.js +50 -45
- package/synthetizer/audio_engine/message_protocol/worklet_message.js +1 -12
- package/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +3 -2
- package/synthetizer/worklet_processor.min.js +12 -12
- package/synthetizer/worklet_wrapper/key_modifier_manager.js +6 -4
- package/synthetizer/{audio_engine/worklet_methods/worklet_soundfont_manager → worklet_wrapper}/sfman_message.js +1 -1
- package/synthetizer/worklet_wrapper/synth_soundfont_manager.js +1 -3
- package/synthetizer/worklet_wrapper/synthetizer.js +6 -6
- package/synthetizer/worklet_wrapper/worklet_processor.js +142 -26
- package/synthetizer/audio_engine/worklet_methods/controller_control/master_parameters.js +0 -36
- /package/{midi_parser → midi}/README.md +0 -0
- /package/{midi_parser → midi}/midi_builder.js +0 -0
- /package/{midi_parser → midi}/midi_data.js +0 -0
- /package/{midi_parser → midi}/midi_message.js +0 -0
- /package/{midi_parser → midi}/midi_sequence.js +0 -0
- /package/synthetizer/audio_engine/{worklet_utilities → engine_components}/lfo.js +0 -0
- /package/synthetizer/audio_engine/{worklet_utilities → engine_components}/modulator_curves.js +0 -0
- /package/synthetizer/audio_engine/{worklet_utilities → engine_components}/unit_converter.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/portamento_time.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/clear_sound_font.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/get_preset.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/reload_sound_font.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/send_preset_list.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/set_embedded_sound_font.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/stop_all_channels.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/transpose_all_channels.js +0 -0
package/index.js
CHANGED
|
@@ -8,18 +8,18 @@ import { Generator } from "./soundfont/basic_soundfont/generator.js";
|
|
|
8
8
|
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
|
-
import { BasicMIDI } from "./
|
|
12
|
-
import { MIDIMessage } from "./
|
|
13
|
-
import { MIDI } from
|
|
14
|
-
import { RMIDINFOChunks } from "./
|
|
15
|
-
import { MIDIBuilder } from "./
|
|
11
|
+
import { BasicMIDI } from "./midi/basic_midi.js";
|
|
12
|
+
import { MIDIMessage } from "./midi/midi_message.js";
|
|
13
|
+
import { MIDI } from "./midi/midi_loader.js";
|
|
14
|
+
import { RMIDINFOChunks } from "./midi/midi_tools/rmidi_writer.js";
|
|
15
|
+
import { MIDIBuilder } from "./midi/midi_builder.js";
|
|
16
16
|
import { Synthetizer } from "./synthetizer/worklet_wrapper/synthetizer.js";
|
|
17
17
|
import { VOICE_CAP, DEFAULT_PERCUSSION } from "./synthetizer/synth_constants.js";
|
|
18
18
|
import { Sequencer } from "./sequencer/worklet_wrapper/sequencer.js";
|
|
19
19
|
import { IndexedByteArray } from './utils/indexed_array.js';
|
|
20
20
|
import { audioBufferToWav } from './utils/buffer_to_wav.js';
|
|
21
21
|
import { SpessaSynthLogging } from './utils/loggin.js';
|
|
22
|
-
import { midiControllers, messageTypes } from
|
|
22
|
+
import { midiControllers, messageTypes } from "./midi/midi_message.js";
|
|
23
23
|
import { MIDIDeviceHandler} from "./external_midi/midi_handler.js";
|
|
24
24
|
import { WebMIDILinkHandler} from "./external_midi/web_midi_link.js";
|
|
25
25
|
import { modulatorSources } from "./soundfont/basic_soundfont/modulator.js";
|
|
@@ -28,7 +28,7 @@ import { WORKLET_URL_ABSOLUTE } from "./synthetizer/worklet_wrapper/worklet_url.
|
|
|
28
28
|
import { SynthesizerSnapshot } from "./synthetizer/audio_engine/snapshot/synthesizer_snapshot.js";
|
|
29
29
|
import { ChannelSnapshot } from "./synthetizer/audio_engine/snapshot/channel_snapshot.js";
|
|
30
30
|
import { SpessaSynthProcessor } from "./synthetizer/audio_engine/main_processor.js";
|
|
31
|
-
import { NON_CC_INDEX_OFFSET } from "./synthetizer/audio_engine/
|
|
31
|
+
import { NON_CC_INDEX_OFFSET } from "./synthetizer/audio_engine/engine_components/controller_tables.js";
|
|
32
32
|
import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from "./synthetizer/audio_engine/message_protocol/worklet_message.js";
|
|
33
33
|
|
|
34
34
|
// Export modules
|
|
@@ -4,11 +4,12 @@ import { messageTypes, MIDIMessage } 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
|
+
import { writeMIDI } from "./midi_tools/midi_writer.js";
|
|
8
|
+
import { applySnapshotToMIDI, modifyMIDI } from "./midi_tools/midi_editor.js";
|
|
9
|
+
import { writeRMIDI } from "./midi_tools/rmidi_writer.js";
|
|
10
|
+
import { getUsedProgramsAndKeys } from "./midi_tools/used_keys_loaded.js";
|
|
11
11
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
12
|
+
import { getNoteTimes } from "./midi_tools/get_note_times.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* BasicMIDI is the base of a complete MIDI file, used by the sequencer internally.
|
|
@@ -559,5 +560,6 @@ BasicMIDI.prototype.modifyMIDI = modifyMIDI;
|
|
|
559
560
|
BasicMIDI.prototype.applySnapshotToMIDI = applySnapshotToMIDI;
|
|
560
561
|
BasicMIDI.prototype.writeRMIDI = writeRMIDI;
|
|
561
562
|
BasicMIDI.prototype.getUsedProgramsAndKeys = getUsedProgramsAndKeys;
|
|
563
|
+
BasicMIDI.prototype.getNoteTimes = getNoteTimes;
|
|
562
564
|
|
|
563
565
|
export { BasicMIDI };
|
|
@@ -7,7 +7,7 @@ import { readVariableLengthQuantity } from "../utils/byte_functions/variable_len
|
|
|
7
7
|
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
8
8
|
import { readBytesAsString } from "../utils/byte_functions/string.js";
|
|
9
9
|
import { readLittleEndian } from "../utils/byte_functions/little_endian.js";
|
|
10
|
-
import { RMIDINFOChunks } from "./rmidi_writer.js";
|
|
10
|
+
import { RMIDINFOChunks } from "./midi_tools/rmidi_writer.js";
|
|
11
11
|
import { BasicMIDI } from "./basic_midi.js";
|
|
12
12
|
import { loadXMF } from "./xmf_loader.js";
|
|
13
13
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
2
|
+
import { readBytesAsUintBigEndian } from "../../utils/byte_functions/big_endian.js";
|
|
3
|
+
import { DEFAULT_PERCUSSION } from "../../synthetizer/synth_constants.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calculates all note times in seconds.
|
|
7
|
+
* @this {BasicMIDI}
|
|
8
|
+
* @param minDrumLength {number} the shortest a drum note (channel 10) can be, in seconds.
|
|
9
|
+
* @returns {{
|
|
10
|
+
* midiNote: number,
|
|
11
|
+
* start: number,
|
|
12
|
+
* length: number,
|
|
13
|
+
* velocity: number,
|
|
14
|
+
* }[][]} an array of 16 channels, each channel containing its notes,
|
|
15
|
+
* with their key number, velocity, absolute start time and length in seconds.
|
|
16
|
+
*/
|
|
17
|
+
export function getNoteTimes(minDrumLength = 0)
|
|
18
|
+
{
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* gets tempo from the midi message
|
|
22
|
+
* @param event {MIDIMessage}
|
|
23
|
+
* @return {number} the tempo in bpm
|
|
24
|
+
*/
|
|
25
|
+
function getTempo(event)
|
|
26
|
+
{
|
|
27
|
+
// simulate IndexedByteArray
|
|
28
|
+
event.messageData = new IndexedByteArray(event.messageData.buffer);
|
|
29
|
+
event.messageData.currentIndex = 0;
|
|
30
|
+
return 60000000 / readBytesAsUintBigEndian(event.messageData, 3);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* an array of 16 arrays (channels)
|
|
35
|
+
* @type {{
|
|
36
|
+
* midiNote: number,
|
|
37
|
+
* start: number,
|
|
38
|
+
* length: number,
|
|
39
|
+
* velocity: number,
|
|
40
|
+
* }[][]}
|
|
41
|
+
*/
|
|
42
|
+
const noteTimes = [];
|
|
43
|
+
// flatten and sort by ticks
|
|
44
|
+
const trackData = this.tracks;
|
|
45
|
+
let events = trackData.flat();
|
|
46
|
+
events.sort((e1, e2) => e1.ticks - e2.ticks);
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < 16; i++)
|
|
49
|
+
{
|
|
50
|
+
noteTimes.push([]);
|
|
51
|
+
}
|
|
52
|
+
let elapsedTime = 0;
|
|
53
|
+
let oneTickToSeconds = 60 / (120 * this.timeDivision);
|
|
54
|
+
let eventIndex = 0;
|
|
55
|
+
let unfinished = 0;
|
|
56
|
+
/**
|
|
57
|
+
* @type {{
|
|
58
|
+
* midiNote: number,
|
|
59
|
+
* start: number,
|
|
60
|
+
* length: number,
|
|
61
|
+
* velocity: number,
|
|
62
|
+
* }[][]}
|
|
63
|
+
*/
|
|
64
|
+
const unfinishedNotes = [];
|
|
65
|
+
for (let i = 0; i < 16; i++)
|
|
66
|
+
{
|
|
67
|
+
unfinishedNotes.push([]);
|
|
68
|
+
}
|
|
69
|
+
const noteOff = (midiNote, channel) =>
|
|
70
|
+
{
|
|
71
|
+
const noteIndex = unfinishedNotes[channel].findIndex(n => n.midiNote === midiNote);
|
|
72
|
+
const note = unfinishedNotes[channel][noteIndex];
|
|
73
|
+
if (note)
|
|
74
|
+
{
|
|
75
|
+
const time = elapsedTime - note.start;
|
|
76
|
+
note.length = time;
|
|
77
|
+
if (channel === DEFAULT_PERCUSSION)
|
|
78
|
+
{
|
|
79
|
+
note.length = time < minDrumLength ? minDrumLength : time;
|
|
80
|
+
}
|
|
81
|
+
// delete from unfinished
|
|
82
|
+
unfinishedNotes[channel].splice(noteIndex, 1);
|
|
83
|
+
}
|
|
84
|
+
unfinished--;
|
|
85
|
+
};
|
|
86
|
+
while (eventIndex < events.length)
|
|
87
|
+
{
|
|
88
|
+
const event = events[eventIndex];
|
|
89
|
+
|
|
90
|
+
const status = event.messageStatusByte >> 4;
|
|
91
|
+
const channel = event.messageStatusByte & 0x0F;
|
|
92
|
+
|
|
93
|
+
// note off
|
|
94
|
+
if (status === 0x8)
|
|
95
|
+
{
|
|
96
|
+
noteOff(event.messageData[0], channel);
|
|
97
|
+
}
|
|
98
|
+
// note on
|
|
99
|
+
else if (status === 0x9)
|
|
100
|
+
{
|
|
101
|
+
if (event.messageData[1] === 0)
|
|
102
|
+
{
|
|
103
|
+
// never mind, its note off
|
|
104
|
+
noteOff(event.messageData[0], channel);
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
{
|
|
108
|
+
// stop previous
|
|
109
|
+
noteOff(event.messageData[0], channel);
|
|
110
|
+
const noteTime = {
|
|
111
|
+
midiNote: event.messageData[0],
|
|
112
|
+
start: elapsedTime,
|
|
113
|
+
length: -1,
|
|
114
|
+
velocity: event.messageData[1] / 127
|
|
115
|
+
};
|
|
116
|
+
noteTimes[channel].push(noteTime);
|
|
117
|
+
unfinishedNotes[channel].push(noteTime);
|
|
118
|
+
unfinished++;
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// set tempo
|
|
123
|
+
else if (event.messageStatusByte === 0x51)
|
|
124
|
+
{
|
|
125
|
+
oneTickToSeconds = 60 / (getTempo(event) * this.timeDivision);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (++eventIndex >= events.length)
|
|
129
|
+
{
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
elapsedTime += oneTickToSeconds * (events[eventIndex].ticks - event.ticks);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// finish the unfinished notes
|
|
137
|
+
if (unfinished > 0)
|
|
138
|
+
{
|
|
139
|
+
// for every channel, for every note that is unfinished (has -1 length)
|
|
140
|
+
unfinishedNotes.forEach((channelNotes, channel) =>
|
|
141
|
+
{
|
|
142
|
+
channelNotes.forEach(note =>
|
|
143
|
+
{
|
|
144
|
+
const time = elapsedTime - note.start;
|
|
145
|
+
note.length = time;
|
|
146
|
+
if (channel === DEFAULT_PERCUSSION)
|
|
147
|
+
{
|
|
148
|
+
note.length = time < minDrumLength ? minDrumLength : time;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return noteTimes;
|
|
154
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { messageTypes, midiControllers, MIDIMessage } from "
|
|
2
|
-
import { IndexedByteArray } from "
|
|
3
|
-
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "
|
|
4
|
-
import { consoleColors } from "
|
|
1
|
+
import { messageTypes, midiControllers, MIDIMessage } from "../midi_message.js";
|
|
2
|
+
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
3
|
+
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
|
|
4
|
+
import { consoleColors } from "../../utils/other.js";
|
|
5
5
|
|
|
6
|
-
import { customControllers } from "
|
|
7
|
-
import { DEFAULT_PERCUSSION } from "
|
|
8
|
-
import { isGM2On, isGMOn, isGSOn, isXGOn } from "
|
|
9
|
-
import { isSystemXG, isXGDrums, XG_SFX_VOICE } from "
|
|
6
|
+
import { customControllers } from "../../synthetizer/audio_engine/engine_components/controller_tables.js";
|
|
7
|
+
import { DEFAULT_PERCUSSION } from "../../synthetizer/synth_constants.js";
|
|
8
|
+
import { isGM2On, isGMOn, isGSOn, isXGOn } from "../../utils/sysex_detector.js";
|
|
9
|
+
import { isSystemXG, isXGDrums, XG_SFX_VOICE } from "../../utils/xg_hacks.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param ticks {number}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { messageTypes } from "
|
|
2
|
-
import { writeVariableLengthQuantity } from "
|
|
3
|
-
import { writeBytesAsUintBigEndian } from "
|
|
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
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Exports the midi as a standard MIDI file
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { combineArrays, IndexedByteArray } from "
|
|
2
|
-
import { writeRIFFOddSize } from "
|
|
3
|
-
import { getStringBytes, getStringBytesZero } from "
|
|
4
|
-
import { messageTypes, midiControllers, MIDIMessage } from "
|
|
1
|
+
import { combineArrays, IndexedByteArray } from "../../utils/indexed_array.js";
|
|
2
|
+
import { writeRIFFOddSize } from "../../soundfont/basic_soundfont/riff_chunk.js";
|
|
3
|
+
import { getStringBytes, getStringBytesZero } from "../../utils/byte_functions/string.js";
|
|
4
|
+
import { messageTypes, midiControllers, MIDIMessage } from "../midi_message.js";
|
|
5
5
|
import { getGsOn } from "./midi_editor.js";
|
|
6
|
-
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "
|
|
7
|
-
import { consoleColors } from "
|
|
8
|
-
import { writeLittleEndian } from "
|
|
9
|
-
import { DEFAULT_PERCUSSION } from "
|
|
10
|
-
import { chooseBank, isSystemXG, parseBankSelect } from "
|
|
11
|
-
import { isGM2On, isGMOn, isGSDrumsOn, isGSOn, isXGOn } from "
|
|
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
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @enum {string}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "
|
|
2
|
-
import { consoleColors } from "
|
|
3
|
-
import { messageTypes, midiControllers } from "
|
|
4
|
-
import { DEFAULT_PERCUSSION } from "
|
|
5
|
-
import { chooseBank, isSystemXG, parseBankSelect } from "
|
|
6
|
-
import { isGSDrumsOn, isXGOn } from "
|
|
1
|
+
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
|
|
2
|
+
import { consoleColors } from "../../utils/other.js";
|
|
3
|
+
import { messageTypes, midiControllers } from "../midi_message.js";
|
|
4
|
+
import { DEFAULT_PERCUSSION } from "../../synthetizer/synth_constants.js";
|
|
5
|
+
import { chooseBank, isSystemXG, parseBankSelect } from "../../utils/xg_hacks.js";
|
|
6
|
+
import { isGSDrumsOn, isXGOn } from "../../utils/sysex_detector.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Gets the used programs and keys for this MIDI file with a given sound bank
|
|
10
10
|
* @this {BasicMIDI}
|
|
11
11
|
* @param soundfont {BasicSoundBank|WorkletSoundfontManager} - the sound bank
|
|
12
|
-
* @returns {Object<string, Set<string>>}
|
|
12
|
+
* @returns {Object<string, Set<string>>} Object<bank:program, Set<key-velocity>>
|
|
13
13
|
*/
|
|
14
14
|
export function getUsedProgramsAndKeys(soundfont)
|
|
15
15
|
{
|
|
@@ -3,7 +3,7 @@ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn
|
|
|
3
3
|
import { consoleColors } from "../utils/other.js";
|
|
4
4
|
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
5
5
|
import { readVariableLengthQuantity } from "../utils/byte_functions/variable_length_quantity.js";
|
|
6
|
-
import { RMIDINFOChunks } from "./rmidi_writer.js";
|
|
6
|
+
import { RMIDINFOChunks } from "./midi_tools/rmidi_writer.js";
|
|
7
7
|
import { inflateSync } from "../externals/fflate/fflate.min.js";
|
|
8
8
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,112 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "../../synthetizer/audio_engine/message_protocol/worklet_message.js";
|
|
5
|
-
import {
|
|
6
|
-
SongChangeType,
|
|
7
|
-
SpessaSynthSequencerMessageType,
|
|
8
|
-
SpessaSynthSequencerReturnMessageType
|
|
9
|
-
} from "../worklet_wrapper/sequencer_message.js";
|
|
10
|
-
import { messageTypes, midiControllers } from "../../midi_parser/midi_message.js";
|
|
1
|
+
import { returnMessageType } from "../../synthetizer/audio_engine/message_protocol/worklet_message.js";
|
|
2
|
+
import { SpessaSynthSequencerReturnMessageType } from "../worklet_wrapper/sequencer_message.js";
|
|
3
|
+
import { messageTypes, midiControllers } from "../../midi/midi_message.js";
|
|
11
4
|
|
|
12
5
|
import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synth_constants.js";
|
|
13
6
|
|
|
14
|
-
/**
|
|
15
|
-
* @param messageType {SpessaSynthSequencerMessageType}
|
|
16
|
-
* @param messageData {any}
|
|
17
|
-
* @this {SpessaSynthSequencer}
|
|
18
|
-
*/
|
|
19
|
-
export function processMessage(messageType, messageData)
|
|
20
|
-
{
|
|
21
|
-
switch (messageType)
|
|
22
|
-
{
|
|
23
|
-
default:
|
|
24
|
-
break;
|
|
25
|
-
|
|
26
|
-
case SpessaSynthSequencerMessageType.loadNewSongList:
|
|
27
|
-
this.loadNewSongList(messageData[0], messageData[1]);
|
|
28
|
-
break;
|
|
29
|
-
|
|
30
|
-
case SpessaSynthSequencerMessageType.pause:
|
|
31
|
-
this.pause();
|
|
32
|
-
break;
|
|
33
|
-
|
|
34
|
-
case SpessaSynthSequencerMessageType.play:
|
|
35
|
-
this.play(messageData);
|
|
36
|
-
break;
|
|
37
|
-
|
|
38
|
-
case SpessaSynthSequencerMessageType.stop:
|
|
39
|
-
this.stop();
|
|
40
|
-
break;
|
|
41
|
-
|
|
42
|
-
case SpessaSynthSequencerMessageType.setTime:
|
|
43
|
-
this.currentTime = messageData;
|
|
44
|
-
break;
|
|
45
|
-
|
|
46
|
-
case SpessaSynthSequencerMessageType.changeMIDIMessageSending:
|
|
47
|
-
this.sendMIDIMessages = messageData;
|
|
48
|
-
break;
|
|
49
|
-
|
|
50
|
-
case SpessaSynthSequencerMessageType.setPlaybackRate:
|
|
51
|
-
this.playbackRate = messageData;
|
|
52
|
-
break;
|
|
53
|
-
|
|
54
|
-
case SpessaSynthSequencerMessageType.setLoop:
|
|
55
|
-
const [loop, count] = messageData;
|
|
56
|
-
this.loop = loop;
|
|
57
|
-
if (count === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
58
|
-
{
|
|
59
|
-
this.loopCount = Infinity;
|
|
60
|
-
}
|
|
61
|
-
else
|
|
62
|
-
{
|
|
63
|
-
this.loopCount = count;
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
|
|
67
|
-
case SpessaSynthSequencerMessageType.changeSong:
|
|
68
|
-
switch (messageData[0])
|
|
69
|
-
{
|
|
70
|
-
case SongChangeType.forwards:
|
|
71
|
-
this.nextSong();
|
|
72
|
-
break;
|
|
73
|
-
|
|
74
|
-
case SongChangeType.backwards:
|
|
75
|
-
this.previousSong();
|
|
76
|
-
break;
|
|
77
|
-
|
|
78
|
-
case SongChangeType.shuffleOff:
|
|
79
|
-
this.shuffleMode = false;
|
|
80
|
-
this.songIndex = this.shuffledSongIndexes[this.songIndex];
|
|
81
|
-
break;
|
|
82
|
-
|
|
83
|
-
case SongChangeType.shuffleOn:
|
|
84
|
-
this.shuffleMode = true;
|
|
85
|
-
this.shuffleSongIndexes();
|
|
86
|
-
this.songIndex = 0;
|
|
87
|
-
this.loadCurrentSong();
|
|
88
|
-
break;
|
|
89
|
-
|
|
90
|
-
case SongChangeType.index:
|
|
91
|
-
this.songIndex = messageData[1];
|
|
92
|
-
this.loadCurrentSong();
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
|
|
97
|
-
case SpessaSynthSequencerMessageType.getMIDI:
|
|
98
|
-
this.post(SpessaSynthSequencerReturnMessageType.getMIDI, this.midiData);
|
|
99
|
-
break;
|
|
100
|
-
|
|
101
|
-
case SpessaSynthSequencerMessageType.setSkipToFirstNote:
|
|
102
|
-
this.skipToFirstNoteOn = messageData;
|
|
103
|
-
break;
|
|
104
|
-
|
|
105
|
-
case SpessaSynthSequencerMessageType.setPreservePlaybackState:
|
|
106
|
-
this.preservePlaybackState = messageData;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
7
|
/**
|
|
111
8
|
*
|
|
112
9
|
* @param messageType {SpessaSynthSequencerReturnMessageType}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { getEvent, messageTypes, midiControllers } from "../../
|
|
1
|
+
import { getEvent, messageTypes, midiControllers } from "../../midi/midi_message.js";
|
|
2
2
|
import { SpessaSynthSequencerReturnMessageType } from "../worklet_wrapper/sequencer_message.js";
|
|
3
|
-
import { resetArray } from "../../synthetizer/audio_engine/
|
|
4
|
-
import {
|
|
5
|
-
nonResetableCCs
|
|
6
|
-
} from "../../synthetizer/audio_engine/worklet_methods/controller_control/reset_controllers.js";
|
|
3
|
+
import { resetArray } from "../../synthetizer/audio_engine/engine_components/controller_tables.js";
|
|
4
|
+
import { nonResetableCCs } from "../../synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js";
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
// an array with preset default values
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getEvent, messageTypes } from "../../
|
|
1
|
+
import { getEvent, messageTypes } from "../../midi/midi_message.js";
|
|
2
2
|
import { SpessaSynthSequencerReturnMessageType } from "../worklet_wrapper/sequencer_message.js";
|
|
3
3
|
import { consoleColors } from "../../utils/other.js";
|
|
4
4
|
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
@@ -3,10 +3,9 @@ import { _addNewMidiPort, _processEvent } from "./process_event.js";
|
|
|
3
3
|
import { _findFirstEventIndex, processTick } from "./process_tick.js";
|
|
4
4
|
import { assignMIDIPort, loadNewSequence, loadNewSongList, nextSong, previousSong } from "./song_control.js";
|
|
5
5
|
import { _playTo, _recalculateStartTime, play, setTimeTicks } from "./play.js";
|
|
6
|
-
import { messageTypes, midiControllers } from "../../
|
|
6
|
+
import { messageTypes, midiControllers } from "../../midi/midi_message.js";
|
|
7
7
|
import {
|
|
8
8
|
post,
|
|
9
|
-
processMessage,
|
|
10
9
|
sendMIDICC,
|
|
11
10
|
sendMIDIMessage,
|
|
12
11
|
sendMIDIPitchWheel,
|
|
@@ -317,7 +316,6 @@ SpessaSynthSequencer.prototype.sendMIDIPitchWheel = sendMIDIPitchWheel;
|
|
|
317
316
|
SpessaSynthSequencer.prototype.assignMIDIPort = assignMIDIPort;
|
|
318
317
|
|
|
319
318
|
SpessaSynthSequencer.prototype.post = post;
|
|
320
|
-
SpessaSynthSequencer.prototype.processMessage = processMessage;
|
|
321
319
|
|
|
322
320
|
SpessaSynthSequencer.prototype._processEvent = _processEvent;
|
|
323
321
|
SpessaSynthSequencer.prototype._addNewMidiPort = _addNewMidiPort;
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
SpessaSynthInfo,
|
|
7
7
|
SpessaSynthWarn
|
|
8
8
|
} from "../../utils/loggin.js";
|
|
9
|
-
import { MIDIData } from "../../
|
|
10
|
-
import { MIDI } from "../../
|
|
11
|
-
import { BasicMIDI } from "../../
|
|
9
|
+
import { MIDIData } from "../../midi/midi_data.js";
|
|
10
|
+
import { MIDI } from "../../midi/midi_loader.js";
|
|
11
|
+
import { BasicMIDI } from "../../midi/basic_midi.js";
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Synthetizer } from "../../synthetizer/worklet_wrapper/synthetizer.js";
|
|
2
|
-
import { messageTypes } from "../../
|
|
2
|
+
import { messageTypes } from "../../midi/midi_message.js";
|
|
3
3
|
import {
|
|
4
4
|
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
5
5
|
workletMessageType
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
SpessaSynthSequencerReturnMessageType
|
|
11
11
|
} from "./sequencer_message.js";
|
|
12
12
|
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
13
|
-
import { DUMMY_MIDI_DATA, MIDIData } from "../../
|
|
14
|
-
import { BasicMIDI } from "../../
|
|
13
|
+
import { DUMMY_MIDI_DATA, MIDIData } from "../../midi/midi_data.js";
|
|
14
|
+
import { BasicMIDI } from "../../midi/basic_midi.js";
|
|
15
15
|
import { readBytesAsUintBigEndian } from "../../utils/byte_functions/big_endian.js";
|
|
16
16
|
import { DEFAULT_SEQUENCER_OPTIONS } from "./default_sequencer_options.js";
|
|
17
17
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { midiControllers } from "../../../
|
|
1
|
+
import { midiControllers } from "../../../midi/midi_message.js";
|
|
2
2
|
import { DLSSources } from "../../dls/dls_sources.js";
|
|
3
3
|
import { modulatorCurveTypes, modulatorSources } from "../modulator.js";
|
|
4
4
|
import { generatorTypes } from "../generator.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DLSSources } from "./dls_sources.js";
|
|
2
2
|
import { getModSourceEnum, Modulator, modulatorCurveTypes, modulatorSources } from "../basic_soundfont/modulator.js";
|
|
3
|
-
import { midiControllers } from "../../
|
|
3
|
+
import { midiControllers } from "../../midi/midi_message.js";
|
|
4
4
|
import { DLSDestinations } from "./dls_destinations.js";
|
|
5
5
|
|
|
6
6
|
import { generatorTypes } from "../basic_soundfont/generator.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
## This is the
|
|
1
|
+
## This is the synthesis engine folder.
|
|
2
2
|
|
|
3
3
|
The code here is responsible for a single midi channel, synthesizing the sound to it.
|
|
4
4
|
|
|
5
|
-
- `
|
|
6
|
-
- `
|
|
5
|
+
- `engine_methods` contains the methods for the `main_processor.js`
|
|
6
|
+
- `engine_components` contains the various digital signal processing functions such as the wavetable oscillator, low
|
|
7
7
|
pass filter, etc.
|
|
8
8
|
|
|
9
9
|
For those interested, `render_voice.js` file contains the actual DSP synthesis code.
|
|
@@ -11,9 +11,9 @@ For those interested, `render_voice.js` file contains the actual DSP synthesis c
|
|
|
11
11
|
`minify_processor.js` uses esbuild to minify the processor code. Importing this instead of `worklet_processor.js` is
|
|
12
12
|
recommended.
|
|
13
13
|
|
|
14
|
-
## How it works
|
|
14
|
+
## How it works in spessasynth_lib
|
|
15
15
|
Both `Synthetizer` and `Sequencer` are essentially "remote control"
|
|
16
|
-
for the actual sequencer and synthesizer in the audio worklet thread
|
|
16
|
+
for the actual sequencer and synthesizer in the audio worklet thread (here)
|
|
17
17
|
These core components are wrapped in the AudioWorkletProcessor, which is receiving both commands and data (MIDIs, sound banks)
|
|
18
18
|
through the message port, and sends data back (events, time changes, status changes, etc.).
|
|
19
19
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { getModulatorCurveValue, MOD_PRECOMPUTED_LENGTH } from "./modulator_curves.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { VolumeEnvelope } from "./volume_envelope.js";
|
|
3
|
+
import { ModulationEnvelope } from "./modulation_envelope.js";
|
|
4
4
|
import { generatorLimits, generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
|
|
5
5
|
import { Modulator, modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
6
6
|
import { NON_CC_INDEX_OFFSET } from "./controller_tables.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* compute_modulator.js
|
|
10
10
|
* purpose: precomputes all curve types and computes modulators
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -16,10 +16,10 @@ const EFFECT_MODULATOR_TRANSFORM_MULTIPLIER = 1000 / 200;
|
|
|
16
16
|
* Computes a given modulator
|
|
17
17
|
* @param controllerTable {Int16Array} all midi controllers as 14bit values + the non-controller indexes, starting at 128
|
|
18
18
|
* @param modulator {Modulator} the modulator to compute
|
|
19
|
-
* @param voice {
|
|
19
|
+
* @param voice {Voice} the voice belonging to the modulator
|
|
20
20
|
* @returns {number} the computed value
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function computeModulator(controllerTable, modulator, voice)
|
|
23
23
|
{
|
|
24
24
|
if (modulator.transformAmount === 0)
|
|
25
25
|
{
|
|
@@ -119,7 +119,7 @@ export function computeWorkletModulator(controllerTable, modulator, voice)
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* Computes modulators of a given voice. Source and index indicate what modulators shall be computed
|
|
122
|
-
* @param voice {
|
|
122
|
+
* @param voice {Voice} the voice to compute modulators for
|
|
123
123
|
* @param controllerTable {Int16Array} all midi controllers as 14bit values + the non-controller indexes, starting at 128
|
|
124
124
|
* @param sourceUsesCC {number} what modulators should be computed, -1 means all, 0 means modulator source enum 1 means midi controller
|
|
125
125
|
* @param sourceIndex {number} enum for the source
|
|
@@ -137,7 +137,7 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
|
|
|
137
137
|
modulators.forEach(mod =>
|
|
138
138
|
{
|
|
139
139
|
const limits = generatorLimits[mod.modulatorDestination];
|
|
140
|
-
const newValue = modulatedGenerators[mod.modulatorDestination] +
|
|
140
|
+
const newValue = modulatedGenerators[mod.modulatorDestination] + computeModulator(
|
|
141
141
|
controllerTable,
|
|
142
142
|
mod,
|
|
143
143
|
voice
|
|
@@ -147,8 +147,8 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
|
|
|
147
147
|
Math.min(newValue, limits.max)
|
|
148
148
|
);
|
|
149
149
|
});
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
VolumeEnvelope.recalculate(voice);
|
|
151
|
+
ModulationEnvelope.recalculate(voice);
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -180,7 +180,7 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
|
|
|
180
180
|
// Reset this destination
|
|
181
181
|
modulatedGenerators[destination] = generators[destination];
|
|
182
182
|
// compute our modulator
|
|
183
|
-
|
|
183
|
+
computeModulator(controllerTable, mod, voice);
|
|
184
184
|
// sum the values of all modulators for this destination
|
|
185
185
|
modulators.forEach(m =>
|
|
186
186
|
{
|
|
@@ -202,10 +202,10 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
|
|
|
202
202
|
// Recalculate volume envelope if necessary
|
|
203
203
|
if ([...computedDestinations].some(dest => volenvNeedsRecalculation.has(dest)))
|
|
204
204
|
{
|
|
205
|
-
|
|
205
|
+
VolumeEnvelope.recalculate(voice);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
ModulationEnvelope.recalculate(voice);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
|