spessasynth_core 1.1.3 → 1.1.4
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/LICENSE +3 -26
- package/README.md +156 -474
- package/index.js +73 -8
- package/package.json +21 -8
- package/src/externals/fflate/LICENSE +21 -0
- package/src/externals/fflate/fflate.min.js +1 -0
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/src/externals/stbvorbis_sync/LICENSE +202 -0
- package/src/externals/stbvorbis_sync/NOTICE +6 -0
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/src/midi/README.md +32 -0
- package/src/midi/basic_midi.js +567 -0
- package/src/midi/midi_builder.js +202 -0
- package/src/midi/midi_loader.js +324 -0
- package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
- package/src/midi/midi_sequence.js +224 -0
- package/src/midi/midi_tools/get_note_times.js +154 -0
- package/src/midi/midi_tools/midi_editor.js +611 -0
- package/src/midi/midi_tools/midi_writer.js +99 -0
- package/src/midi/midi_tools/rmidi_writer.js +567 -0
- package/src/midi/midi_tools/used_keys_loaded.js +238 -0
- package/src/midi/xmf_loader.js +454 -0
- package/src/sequencer/README.md +5 -0
- package/src/sequencer/events.js +81 -0
- package/src/sequencer/play.js +349 -0
- package/src/sequencer/process_event.js +165 -0
- package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
- package/src/sequencer/sequencer_engine.js +367 -0
- package/src/sequencer/song_control.js +201 -0
- package/src/soundfont/README.md +13 -0
- package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
- package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
- package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
- package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
- package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
- package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
- package/src/soundfont/basic_soundfont/generator.js +220 -0
- package/src/soundfont/basic_soundfont/modulator.js +378 -0
- package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
- package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
- package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
- package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
- package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
- package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
- package/src/soundfont/dls/articulator_converter.js +396 -0
- package/src/soundfont/dls/dls_destinations.js +38 -0
- package/src/soundfont/dls/dls_preset.js +44 -0
- package/src/soundfont/dls/dls_sample.js +75 -0
- package/src/soundfont/dls/dls_soundfont.js +186 -0
- package/src/soundfont/dls/dls_sources.js +62 -0
- package/src/soundfont/dls/dls_zone.js +95 -0
- package/src/soundfont/dls/read_articulation.js +299 -0
- package/src/soundfont/dls/read_instrument.js +121 -0
- package/src/soundfont/dls/read_instrument_list.js +17 -0
- package/src/soundfont/dls/read_lart.js +35 -0
- package/src/soundfont/dls/read_region.js +152 -0
- package/src/soundfont/dls/read_samples.js +270 -0
- package/src/soundfont/load_soundfont.js +21 -0
- package/src/soundfont/read_sf2/generators.js +46 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
- package/src/soundfont/read_sf2/modulators.js +36 -0
- package/src/soundfont/read_sf2/presets.js +80 -0
- package/src/soundfont/read_sf2/samples.js +304 -0
- package/src/soundfont/read_sf2/soundfont.js +305 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
- package/src/synthetizer/README.md +7 -0
- package/src/synthetizer/audio_engine/README.md +9 -0
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
- package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
- package/src/synthetizer/audio_engine/main_processor.js +804 -0
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
- package/src/synthetizer/synth_constants.js +22 -0
- package/{spessasynth_core → src}/utils/README.md +1 -0
- package/src/utils/buffer_to_wav.js +185 -0
- package/src/utils/byte_functions/big_endian.js +32 -0
- package/src/utils/byte_functions/little_endian.js +77 -0
- package/src/utils/byte_functions/string.js +107 -0
- package/src/utils/byte_functions/variable_length_quantity.js +42 -0
- package/src/utils/fill_with_defaults.js +21 -0
- package/src/utils/indexed_array.js +52 -0
- package/{spessasynth_core → src}/utils/loggin.js +70 -78
- package/src/utils/other.js +92 -0
- package/src/utils/sysex_detector.js +58 -0
- package/src/utils/xg_hacks.js +193 -0
- package/.idea/inspectionProfiles/Project_Default.xml +0 -10
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/spessasynth_core.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/spessasynth_core/midi_parser/README.md +0 -3
- package/spessasynth_core/midi_parser/midi_loader.js +0 -386
- package/spessasynth_core/sequencer/sequencer.js +0 -202
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
- package/spessasynth_core/soundfont/README.md +0 -4
- package/spessasynth_core/soundfont/chunk/generators.js +0 -205
- package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
- package/spessasynth_core/soundfont/chunk/presets.js +0 -264
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
- package/spessasynth_core/soundfont/chunk/samples.js +0 -250
- package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
- package/spessasynth_core/synthetizer/README.md +0 -6
- package/spessasynth_core/synthetizer/synthesizer.js +0 -313
- package/spessasynth_core/synthetizer/worklet_system/README.md +0 -3
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -290
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -280
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +0 -102
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +0 -77
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +0 -140
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -266
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -104
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +0 -223
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -133
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +0 -73
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -76
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -272
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -83
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -175
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +0 -106
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +0 -285
- package/spessasynth_core/utils/buffer_to_wav.js +0 -70
- package/spessasynth_core/utils/byte_functions.js +0 -141
- package/spessasynth_core/utils/other.js +0 -49
- package/spessasynth_core/utils/shiftable_array.js +0 -26
- package/spessasynth_core/utils/stbvorbis_sync.js +0 -1877
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { consoleColors, formatTime } from "../utils/other.js";
|
|
2
|
+
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
|
|
3
|
+
import { BasicMIDI } from "../midi/basic_midi.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param trackNum {number}
|
|
7
|
+
* @param port {number}
|
|
8
|
+
* @this {SpessaSynthSequencer}
|
|
9
|
+
*/
|
|
10
|
+
export function assignMIDIPort(trackNum, port)
|
|
11
|
+
{
|
|
12
|
+
// do not assign ports to empty tracks
|
|
13
|
+
if (this.midiData.usedChannelsOnTrack[trackNum].size === 0)
|
|
14
|
+
{
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// assign new 16 channels if the port is not occupied yet
|
|
19
|
+
if (this.midiPortChannelOffset === 0)
|
|
20
|
+
{
|
|
21
|
+
this.midiPortChannelOffset += 16;
|
|
22
|
+
this.midiPortChannelOffsets[port] = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (this.midiPortChannelOffsets[port] === undefined)
|
|
26
|
+
{
|
|
27
|
+
if (this.synth.midiAudioChannels.length < this.midiPortChannelOffset + 15)
|
|
28
|
+
{
|
|
29
|
+
this._addNewMidiPort();
|
|
30
|
+
}
|
|
31
|
+
this.midiPortChannelOffsets[port] = this.midiPortChannelOffset;
|
|
32
|
+
this.midiPortChannelOffset += 16;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.midiPorts[trackNum] = port;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Loads a new sequence
|
|
40
|
+
* @param parsedMidi {BasicMIDI}
|
|
41
|
+
* @param autoPlay {boolean}
|
|
42
|
+
* @this {SpessaSynthSequencer}
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
export function loadNewSequence(parsedMidi, autoPlay = true)
|
|
46
|
+
{
|
|
47
|
+
this.stop();
|
|
48
|
+
if (!parsedMidi.tracks)
|
|
49
|
+
{
|
|
50
|
+
throw new Error("This MIDI has no tracks!");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @type {BasicMIDI}
|
|
57
|
+
*/
|
|
58
|
+
this.midiData = parsedMidi;
|
|
59
|
+
|
|
60
|
+
// check for embedded soundfont
|
|
61
|
+
if (this.midiData.embeddedSoundFont !== undefined)
|
|
62
|
+
{
|
|
63
|
+
SpessaSynthInfo("%cEmbedded soundfont detected! Using it.", consoleColors.recognized);
|
|
64
|
+
this.synth.setEmbeddedSoundFont(this.midiData.embeddedSoundFont, this.midiData.bankOffset);
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
{
|
|
68
|
+
if (this.synth.overrideSoundfont)
|
|
69
|
+
{
|
|
70
|
+
// clean up the embedded soundfont
|
|
71
|
+
this.synth.clearSoundFont(true, true);
|
|
72
|
+
}
|
|
73
|
+
SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
|
|
74
|
+
// smart preloading: load only samples used in the midi!
|
|
75
|
+
const used = this.midiData.getUsedProgramsAndKeys(this.synth.soundfontManager);
|
|
76
|
+
for (const [programBank, combos] of Object.entries(used))
|
|
77
|
+
{
|
|
78
|
+
const bank = parseInt(programBank.split(":")[0]);
|
|
79
|
+
const program = parseInt(programBank.split(":")[1]);
|
|
80
|
+
const preset = this.synth.getPreset(bank, program);
|
|
81
|
+
SpessaSynthInfo(
|
|
82
|
+
`%cPreloading used samples on %c${preset.presetName}%c...`,
|
|
83
|
+
consoleColors.info,
|
|
84
|
+
consoleColors.recognized,
|
|
85
|
+
consoleColors.info
|
|
86
|
+
);
|
|
87
|
+
for (const combo of combos)
|
|
88
|
+
{
|
|
89
|
+
const split = combo.split("-");
|
|
90
|
+
preset.preloadSpecific(parseInt(split[0]), parseInt(split[1]));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
SpessaSynthGroupEnd();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* the midi track data
|
|
98
|
+
* @type {MIDIMessage[][]}
|
|
99
|
+
*/
|
|
100
|
+
this.tracks = this.midiData.tracks;
|
|
101
|
+
|
|
102
|
+
// copy over the port data
|
|
103
|
+
this.midiPorts = this.midiData.midiPorts.slice();
|
|
104
|
+
|
|
105
|
+
// clear last port data
|
|
106
|
+
this.midiPortChannelOffset = 0;
|
|
107
|
+
this.midiPortChannelOffsets = {};
|
|
108
|
+
// assign port offsets
|
|
109
|
+
this.midiData.midiPorts.forEach((port, trackIndex) =>
|
|
110
|
+
{
|
|
111
|
+
this.assignMIDIPort(trackIndex, port);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Same as "audio.duration" property (seconds)
|
|
116
|
+
* @type {number}
|
|
117
|
+
*/
|
|
118
|
+
this.duration = this.midiData.duration;
|
|
119
|
+
this.firstNoteTime = this.midiData.MIDIticksToSeconds(this.midiData.firstNoteOn);
|
|
120
|
+
SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
|
|
121
|
+
this?.onSongChange?.(this.songIndex, autoPlay);
|
|
122
|
+
|
|
123
|
+
if (this.duration <= 1)
|
|
124
|
+
{
|
|
125
|
+
SpessaSynthWarn(
|
|
126
|
+
`%cVery short song: (${formatTime(Math.round(this.duration)).time}). Disabling loop!`,
|
|
127
|
+
consoleColors.warn
|
|
128
|
+
);
|
|
129
|
+
this.loop = false;
|
|
130
|
+
}
|
|
131
|
+
if (autoPlay)
|
|
132
|
+
{
|
|
133
|
+
this.play(true);
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
{
|
|
137
|
+
// this shall not play: play to the first note and then wait
|
|
138
|
+
const targetTime = this.skipToFirstNoteOn ? this.midiData.firstNoteOn - 1 : 0;
|
|
139
|
+
this.setTimeTicks(targetTime);
|
|
140
|
+
this.pause();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param midiBuffers {BasicMIDI[]}
|
|
146
|
+
* @param autoPlay {boolean}
|
|
147
|
+
* @this {SpessaSynthSequencer}
|
|
148
|
+
*/
|
|
149
|
+
export function loadNewSongList(midiBuffers, autoPlay = true)
|
|
150
|
+
{
|
|
151
|
+
/**
|
|
152
|
+
* parse the MIDIs (only the array buffers, MIDI is unchanged)
|
|
153
|
+
* @type {BasicMIDI[]}
|
|
154
|
+
*/
|
|
155
|
+
this.songs = midiBuffers.map(m => BasicMIDI.copyFrom(m));
|
|
156
|
+
if (this.songs.length < 1)
|
|
157
|
+
{
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.songIndex = 0;
|
|
161
|
+
if (this.songs.length > 1)
|
|
162
|
+
{
|
|
163
|
+
this.loop = false;
|
|
164
|
+
}
|
|
165
|
+
this.shuffleSongIndexes();
|
|
166
|
+
this?.onSongListChange?.(this.songs);
|
|
167
|
+
this.loadCurrentSong(autoPlay);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @this {SpessaSynthSequencer}
|
|
172
|
+
*/
|
|
173
|
+
export function nextSong()
|
|
174
|
+
{
|
|
175
|
+
if (this.songs.length === 1)
|
|
176
|
+
{
|
|
177
|
+
this.currentTime = 0;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.songIndex++;
|
|
181
|
+
this.songIndex %= this.songs.length;
|
|
182
|
+
this.loadCurrentSong();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @this {SpessaSynthSequencer}
|
|
187
|
+
*/
|
|
188
|
+
export function previousSong()
|
|
189
|
+
{
|
|
190
|
+
if (this.songs.length === 1)
|
|
191
|
+
{
|
|
192
|
+
this.currentTime = 0;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.songIndex--;
|
|
196
|
+
if (this.songIndex < 0)
|
|
197
|
+
{
|
|
198
|
+
this.songIndex = this.songs.length - 1;
|
|
199
|
+
}
|
|
200
|
+
this.loadCurrentSong();
|
|
201
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## This is the SoundFont2 parsing library.
|
|
2
|
+
|
|
3
|
+
The code here is responsible for parsing the SoundFont2 file and
|
|
4
|
+
providing an easy way to get the data out.
|
|
5
|
+
Default modulators are also stored here (in `modulators.js`)
|
|
6
|
+
|
|
7
|
+
`basic_soundfont` folder contains the classes that represent the soundfont file.
|
|
8
|
+
|
|
9
|
+
`read_sf2` folder contains the code for reading an `.sf2` file.
|
|
10
|
+
|
|
11
|
+
`write` folder contains the code for writing out an `.sf2` file.
|
|
12
|
+
|
|
13
|
+
`dls` folder contains the code for reading a `.dls` file (and converting in into a soundfont representation).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export class BasicInstrument
|
|
2
|
+
{
|
|
3
|
+
/**
|
|
4
|
+
* The instrument's name
|
|
5
|
+
* @type {string}
|
|
6
|
+
*/
|
|
7
|
+
instrumentName = "";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The instrument's zones
|
|
11
|
+
* @type {BasicInstrumentZone[]}
|
|
12
|
+
*/
|
|
13
|
+
instrumentZones = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Instrument's use count, used for trimming
|
|
17
|
+
* @type {number}
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
_useCount = 0;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @returns {number}
|
|
24
|
+
*/
|
|
25
|
+
get useCount()
|
|
26
|
+
{
|
|
27
|
+
return this._useCount;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addUseCount()
|
|
31
|
+
{
|
|
32
|
+
this._useCount++;
|
|
33
|
+
this.instrumentZones.forEach(z => z.useCount++);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
removeUseCount()
|
|
37
|
+
{
|
|
38
|
+
this._useCount--;
|
|
39
|
+
for (let i = 0; i < this.instrumentZones.length; i++)
|
|
40
|
+
{
|
|
41
|
+
if (this.safeDeleteZone(i))
|
|
42
|
+
{
|
|
43
|
+
i--;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deleteInstrument()
|
|
49
|
+
{
|
|
50
|
+
this.instrumentZones.forEach(z => z.deleteZone());
|
|
51
|
+
this.instrumentZones.length = 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param index {number}
|
|
56
|
+
* @returns {boolean} is the zone has been deleted
|
|
57
|
+
*/
|
|
58
|
+
safeDeleteZone(index)
|
|
59
|
+
{
|
|
60
|
+
this.instrumentZones[index].useCount--;
|
|
61
|
+
if (this.instrumentZones[index].useCount < 1)
|
|
62
|
+
{
|
|
63
|
+
this.deleteZone(index);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param index {number}
|
|
71
|
+
*/
|
|
72
|
+
deleteZone(index)
|
|
73
|
+
{
|
|
74
|
+
this.instrumentZones[index].deleteZone();
|
|
75
|
+
this.instrumentZones.splice(index, 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* instrumentGenerators: Generator[],
|
|
4
|
+
* presetGenerators: Generator[],
|
|
5
|
+
* modulators: Modulator[],
|
|
6
|
+
* sample: BasicSample,
|
|
7
|
+
* sampleID: number,
|
|
8
|
+
* }} SampleAndGenerators
|
|
9
|
+
*/
|
|
10
|
+
import { generatorTypes } from "./generator.js";
|
|
11
|
+
import { Modulator } from "./modulator.js";
|
|
12
|
+
import { isXGDrums } from "../../utils/xg_hacks.js";
|
|
13
|
+
|
|
14
|
+
export class BasicPreset
|
|
15
|
+
{
|
|
16
|
+
/**
|
|
17
|
+
* The parent soundbank instance
|
|
18
|
+
* Currently used for determining default modulators and XG status
|
|
19
|
+
* @type {BasicSoundBank}
|
|
20
|
+
*/
|
|
21
|
+
parentSoundBank;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The preset's name
|
|
25
|
+
* @type {string}
|
|
26
|
+
*/
|
|
27
|
+
presetName = "";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The preset's MIDI program number
|
|
31
|
+
* @type {number}
|
|
32
|
+
*/
|
|
33
|
+
program = 0;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The preset's MIDI bank number
|
|
37
|
+
* @type {number}
|
|
38
|
+
*/
|
|
39
|
+
bank = 0;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The preset's zones
|
|
43
|
+
* @type {BasicPresetZone[]}
|
|
44
|
+
*/
|
|
45
|
+
presetZones = [];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stores already found getSamplesAndGenerators for reuse
|
|
49
|
+
* @type {SampleAndGenerators[][][]}
|
|
50
|
+
*/
|
|
51
|
+
foundSamplesAndGenerators = [];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* unused metadata
|
|
55
|
+
* @type {number}
|
|
56
|
+
*/
|
|
57
|
+
library = 0;
|
|
58
|
+
/**
|
|
59
|
+
* unused metadata
|
|
60
|
+
* @type {number}
|
|
61
|
+
*/
|
|
62
|
+
genre = 0;
|
|
63
|
+
/**
|
|
64
|
+
* unused metadata
|
|
65
|
+
* @type {number}
|
|
66
|
+
*/
|
|
67
|
+
morphology = 0;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new preset representation
|
|
71
|
+
* @param parentSoundBank {BasicSoundBank}
|
|
72
|
+
*/
|
|
73
|
+
constructor(parentSoundBank)
|
|
74
|
+
{
|
|
75
|
+
this.parentSoundBank = parentSoundBank;
|
|
76
|
+
for (let i = 0; i < 128; i++)
|
|
77
|
+
{
|
|
78
|
+
this.foundSamplesAndGenerators[i] = [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param allowXG {boolean}
|
|
84
|
+
* @param allowSFX {boolean}
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
isDrumPreset(allowXG, allowSFX = false)
|
|
88
|
+
{
|
|
89
|
+
const xg = allowXG && this.parentSoundBank.isXGBank;
|
|
90
|
+
// sfx is not cool
|
|
91
|
+
return this.bank === 128 || (
|
|
92
|
+
xg &&
|
|
93
|
+
(isXGDrums(this.bank) && (this.bank !== 126 || allowSFX))
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
deletePreset()
|
|
98
|
+
{
|
|
99
|
+
this.presetZones.forEach(z => z.deleteZone());
|
|
100
|
+
this.presetZones.length = 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param index {number}
|
|
105
|
+
*/
|
|
106
|
+
deleteZone(index)
|
|
107
|
+
{
|
|
108
|
+
this.presetZones[index].deleteZone();
|
|
109
|
+
this.presetZones.splice(index, 1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// noinspection JSUnusedGlobalSymbols
|
|
113
|
+
/**
|
|
114
|
+
* Preloads all samples (async)
|
|
115
|
+
*/
|
|
116
|
+
preload(keyMin, keyMax)
|
|
117
|
+
{
|
|
118
|
+
for (let key = keyMin; key < keyMax + 1; key++)
|
|
119
|
+
{
|
|
120
|
+
for (let velocity = 0; velocity < 128; velocity++)
|
|
121
|
+
{
|
|
122
|
+
this.getSamplesAndGenerators(key, velocity).forEach(samandgen =>
|
|
123
|
+
{
|
|
124
|
+
if (!samandgen.sample.isSampleLoaded)
|
|
125
|
+
{
|
|
126
|
+
samandgen.sample.getAudioData();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Preloads a specific key/velocity combo
|
|
135
|
+
* @param key {number}
|
|
136
|
+
* @param velocity {number}
|
|
137
|
+
*/
|
|
138
|
+
preloadSpecific(key, velocity)
|
|
139
|
+
{
|
|
140
|
+
this.getSamplesAndGenerators(key, velocity).forEach(samandgen =>
|
|
141
|
+
{
|
|
142
|
+
if (!samandgen.sample.isSampleLoaded)
|
|
143
|
+
{
|
|
144
|
+
samandgen.sample.getAudioData();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns generatorTranslator and generators for given note
|
|
151
|
+
* @param midiNote {number}
|
|
152
|
+
* @param velocity {number}
|
|
153
|
+
* @returns {SampleAndGenerators[]}
|
|
154
|
+
*/
|
|
155
|
+
getSamplesAndGenerators(midiNote, velocity)
|
|
156
|
+
{
|
|
157
|
+
const memorized = this.foundSamplesAndGenerators[midiNote][velocity];
|
|
158
|
+
if (memorized)
|
|
159
|
+
{
|
|
160
|
+
return memorized;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (this.presetZones.length < 1)
|
|
164
|
+
{
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param range {SoundFontRange}
|
|
170
|
+
* @param number {number}
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
173
|
+
function isInRange(range, number)
|
|
174
|
+
{
|
|
175
|
+
return number >= range.min && number <= range.max;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param main {Generator[]}
|
|
180
|
+
* @param adder {Generator[]}
|
|
181
|
+
*/
|
|
182
|
+
function addUnique(main, adder)
|
|
183
|
+
{
|
|
184
|
+
main.push(...adder.filter(g => !main.find(mg => mg.generatorType === g.generatorType)));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param main {Modulator[]}
|
|
189
|
+
* @param adder {Modulator[]}
|
|
190
|
+
*/
|
|
191
|
+
function addUniqueMods(main, adder)
|
|
192
|
+
{
|
|
193
|
+
main.push(...adder.filter(m => !main.find(mm => Modulator.isIdentical(m, mm))));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @type {SampleAndGenerators[]}
|
|
198
|
+
*/
|
|
199
|
+
let parsedGeneratorsAndSamples = [];
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* global zone is always first, so it or nothing
|
|
203
|
+
* @type {Generator[]}
|
|
204
|
+
*/
|
|
205
|
+
let globalPresetGenerators = this.presetZones[0].isGlobal ? [...this.presetZones[0].generators] : [];
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @type {Modulator[]}
|
|
209
|
+
*/
|
|
210
|
+
let globalPresetModulators = this.presetZones[0].isGlobal ? [...this.presetZones[0].modulators] : [];
|
|
211
|
+
const globalKeyRange = this.presetZones[0].isGlobal ? this.presetZones[0].keyRange : { min: 0, max: 127 };
|
|
212
|
+
const globalVelRange = this.presetZones[0].isGlobal ? this.presetZones[0].velRange : { min: 0, max: 127 };
|
|
213
|
+
|
|
214
|
+
// find the preset zones in range
|
|
215
|
+
let presetZonesInRange = this.presetZones.filter(currentZone =>
|
|
216
|
+
(
|
|
217
|
+
isInRange(
|
|
218
|
+
currentZone.hasKeyRange ? currentZone.keyRange : globalKeyRange,
|
|
219
|
+
midiNote
|
|
220
|
+
)
|
|
221
|
+
&&
|
|
222
|
+
isInRange(
|
|
223
|
+
currentZone.hasVelRange ? currentZone.velRange : globalVelRange,
|
|
224
|
+
velocity
|
|
225
|
+
)
|
|
226
|
+
) && !currentZone.isGlobal);
|
|
227
|
+
|
|
228
|
+
presetZonesInRange.forEach(zone =>
|
|
229
|
+
{
|
|
230
|
+
// the global zone is already taken into account earlier
|
|
231
|
+
if (zone.instrument.instrumentZones.length < 1)
|
|
232
|
+
{
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
let presetGenerators = zone.generators;
|
|
236
|
+
let presetModulators = zone.modulators;
|
|
237
|
+
const firstZone = zone.instrument.instrumentZones[0];
|
|
238
|
+
/**
|
|
239
|
+
* global zone is always first, so it or nothing
|
|
240
|
+
* @type {Generator[]}
|
|
241
|
+
*/
|
|
242
|
+
let globalInstrumentGenerators = firstZone.isGlobal ? [...firstZone.generators] : [];
|
|
243
|
+
let globalInstrumentModulators = firstZone.isGlobal ? [...firstZone.modulators] : [];
|
|
244
|
+
const globalKeyRange = firstZone.isGlobal ? firstZone.keyRange : { min: 0, max: 127 };
|
|
245
|
+
const globalVelRange = firstZone.isGlobal ? firstZone.velRange : { min: 0, max: 127 };
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
let instrumentZonesInRange = zone.instrument.instrumentZones
|
|
249
|
+
.filter(currentZone =>
|
|
250
|
+
(
|
|
251
|
+
isInRange(
|
|
252
|
+
currentZone.hasKeyRange ? currentZone.keyRange : globalKeyRange,
|
|
253
|
+
midiNote
|
|
254
|
+
)
|
|
255
|
+
&&
|
|
256
|
+
isInRange(
|
|
257
|
+
currentZone.hasVelRange ? currentZone.velRange : globalVelRange,
|
|
258
|
+
velocity
|
|
259
|
+
)
|
|
260
|
+
) && !currentZone.isGlobal
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
instrumentZonesInRange.forEach(instrumentZone =>
|
|
264
|
+
{
|
|
265
|
+
let instrumentGenerators = [...instrumentZone.generators];
|
|
266
|
+
let instrumentModulators = [...instrumentZone.modulators];
|
|
267
|
+
|
|
268
|
+
addUnique(
|
|
269
|
+
presetGenerators,
|
|
270
|
+
globalPresetGenerators
|
|
271
|
+
);
|
|
272
|
+
// add the unique global preset generators (local replace global(
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
// add the unique global instrument generators (local replace global)
|
|
276
|
+
addUnique(
|
|
277
|
+
instrumentGenerators,
|
|
278
|
+
globalInstrumentGenerators
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
addUniqueMods(
|
|
282
|
+
presetModulators,
|
|
283
|
+
globalPresetModulators
|
|
284
|
+
);
|
|
285
|
+
addUniqueMods(
|
|
286
|
+
instrumentModulators,
|
|
287
|
+
globalInstrumentModulators
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// default mods
|
|
291
|
+
addUniqueMods(
|
|
292
|
+
instrumentModulators,
|
|
293
|
+
this.parentSoundBank.defaultModulators
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* sum preset modulators to instruments (amount) sf spec page 54
|
|
298
|
+
* @type {Modulator[]}
|
|
299
|
+
*/
|
|
300
|
+
const finalModulatorList = [...instrumentModulators];
|
|
301
|
+
for (let i = 0; i < presetModulators.length; i++)
|
|
302
|
+
{
|
|
303
|
+
let mod = presetModulators[i];
|
|
304
|
+
const identicalInstrumentModulator = finalModulatorList.findIndex(
|
|
305
|
+
m => Modulator.isIdentical(mod, m));
|
|
306
|
+
if (identicalInstrumentModulator !== -1)
|
|
307
|
+
{
|
|
308
|
+
// sum the amounts
|
|
309
|
+
// (this makes a new modulator because otherwise it would overwrite the one in the soundfont!
|
|
310
|
+
finalModulatorList[identicalInstrumentModulator] = finalModulatorList[identicalInstrumentModulator].sumTransform(
|
|
311
|
+
mod);
|
|
312
|
+
}
|
|
313
|
+
else
|
|
314
|
+
{
|
|
315
|
+
finalModulatorList.push(mod);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
// combine both generators and add to the final result
|
|
321
|
+
parsedGeneratorsAndSamples.push({
|
|
322
|
+
instrumentGenerators: instrumentGenerators,
|
|
323
|
+
presetGenerators: presetGenerators,
|
|
324
|
+
modulators: finalModulatorList,
|
|
325
|
+
sample: instrumentZone.sample,
|
|
326
|
+
sampleID: instrumentZone.generators.find(
|
|
327
|
+
g => g.generatorType === generatorTypes.sampleID).generatorValue
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// save and return
|
|
333
|
+
this.foundSamplesAndGenerators[midiNote][velocity] = parsedGeneratorsAndSamples;
|
|
334
|
+
return parsedGeneratorsAndSamples;
|
|
335
|
+
}
|
|
336
|
+
}
|