spessasynth_core 1.1.3 → 1.1.5
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 +74 -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { modulatorCurveTypes } from
|
|
1
|
+
import { modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* modulator_curves.js
|
|
@@ -8,22 +8,21 @@ import { modulatorCurveTypes } from '../../../soundfont/chunk/modulators.js'
|
|
|
8
8
|
// the length of the precomputed curve tables
|
|
9
9
|
export const MOD_PRECOMPUTED_LENGTH = 16384;
|
|
10
10
|
|
|
11
|
-
// Precalculate lookup tables for concave and
|
|
12
|
-
const concave = new Float32Array(MOD_PRECOMPUTED_LENGTH);
|
|
13
|
-
const convex = new Float32Array(MOD_PRECOMPUTED_LENGTH);
|
|
11
|
+
// Precalculate lookup tables for concave and convex curves
|
|
12
|
+
const concave = new Float32Array(MOD_PRECOMPUTED_LENGTH + 1);
|
|
13
|
+
const convex = new Float32Array(MOD_PRECOMPUTED_LENGTH + 1);
|
|
14
14
|
// the equation is taken from FluidSynth as it's the standard for soundFonts
|
|
15
|
-
// more precisely,
|
|
16
|
-
// https://github.com/FluidSynth/fluidsynth/blob/cb8da1e1e2c0a5cff2bab6a419755b598b793384/src/gentables/gen_conv.c#L55
|
|
15
|
+
// more precisely, the gen_conv.c file
|
|
17
16
|
concave[0] = 0;
|
|
18
|
-
concave[
|
|
17
|
+
concave[concave.length - 1] = 1;
|
|
19
18
|
|
|
20
19
|
convex[0] = 0;
|
|
21
|
-
convex[
|
|
22
|
-
for(let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
|
|
20
|
+
convex[convex.length - 1] = 1;
|
|
21
|
+
for (let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
|
|
23
22
|
{
|
|
24
|
-
let x = (-200 * 2 / 960) * Math.log(i / (
|
|
23
|
+
let x = (-200 * 2 / 960) * Math.log(i / (concave.length - 1)) / Math.LN10;
|
|
25
24
|
convex[i] = 1 - x;
|
|
26
|
-
concave[
|
|
25
|
+
concave[concave.length - 1 - i] = x;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
/**
|
|
@@ -32,52 +31,56 @@ for(let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
|
|
|
32
31
|
* @param direction {number} 0 or 1
|
|
33
32
|
* @param curveType {number} see modulatorCurveTypes in modulators.js
|
|
34
33
|
* @param value {number} the linear value, 0 to 1
|
|
35
|
-
* @returns {number} the transformed value, 0 to 1 or -1 to 1
|
|
34
|
+
* @returns {number} the transformed value, 0 to 1, or -1 to 1
|
|
36
35
|
*/
|
|
37
|
-
export function getModulatorCurveValue(direction, curveType, value, polarity)
|
|
36
|
+
export function getModulatorCurveValue(direction, curveType, value, polarity)
|
|
37
|
+
{
|
|
38
38
|
// inverse the value if needed
|
|
39
|
-
if(direction)
|
|
39
|
+
if (direction)
|
|
40
40
|
{
|
|
41
|
-
value = 1 - value
|
|
41
|
+
value = 1 - value;
|
|
42
42
|
}
|
|
43
|
-
switch (curveType)
|
|
43
|
+
switch (curveType)
|
|
44
|
+
{
|
|
44
45
|
case modulatorCurveTypes.linear:
|
|
45
|
-
if (polarity)
|
|
46
|
-
|
|
46
|
+
if (polarity)
|
|
47
|
+
{
|
|
48
|
+
// bipolar curve
|
|
47
49
|
return value * 2 - 1;
|
|
48
50
|
}
|
|
49
51
|
return value;
|
|
50
|
-
|
|
52
|
+
|
|
51
53
|
case modulatorCurveTypes.switch:
|
|
52
54
|
// switch
|
|
53
55
|
value = value > 0.5 ? 1 : 0;
|
|
54
|
-
if (polarity)
|
|
56
|
+
if (polarity)
|
|
57
|
+
{
|
|
55
58
|
// multiply
|
|
56
59
|
return value * 2 - 1;
|
|
57
60
|
}
|
|
58
61
|
return value;
|
|
59
|
-
|
|
62
|
+
|
|
60
63
|
case modulatorCurveTypes.concave:
|
|
61
64
|
// look up the value
|
|
62
|
-
if(polarity)
|
|
65
|
+
if (polarity)
|
|
63
66
|
{
|
|
64
67
|
value = value * 2 - 1;
|
|
65
|
-
if(value < 0)
|
|
68
|
+
if (value < 0)
|
|
66
69
|
{
|
|
67
|
-
return
|
|
70
|
+
return -concave[~~(value * -MOD_PRECOMPUTED_LENGTH)];
|
|
68
71
|
}
|
|
69
|
-
return concave[~~value * MOD_PRECOMPUTED_LENGTH];
|
|
72
|
+
return concave[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
70
73
|
}
|
|
71
|
-
return concave[~~(value * MOD_PRECOMPUTED_LENGTH)]
|
|
72
|
-
|
|
74
|
+
return concave[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
75
|
+
|
|
73
76
|
case modulatorCurveTypes.convex:
|
|
74
77
|
// look up the value
|
|
75
|
-
if(polarity)
|
|
78
|
+
if (polarity)
|
|
76
79
|
{
|
|
77
80
|
value = value * 2 - 1;
|
|
78
|
-
if(value < 0)
|
|
81
|
+
if (value < 0)
|
|
79
82
|
{
|
|
80
|
-
return
|
|
83
|
+
return -convex[~~(value * -MOD_PRECOMPUTED_LENGTH)];
|
|
81
84
|
}
|
|
82
85
|
return convex[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
83
86
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
2
|
+
import { loadSoundFont } from "../../../soundfont/load_soundfont.js";
|
|
3
|
+
import { isXGDrums } from "../../../utils/xg_hacks.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} SoundFontType
|
|
7
|
+
* @property {string} id - unique id for the soundfont
|
|
8
|
+
* @property {BasicSoundBank} soundfont - the soundfont itself
|
|
9
|
+
* @property {number} bankOffset - the soundfont's bank offset
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class WorkletSoundfontManager
|
|
13
|
+
{
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new instance of worklet soundfont manager (worklet scope)
|
|
16
|
+
* @param initialSoundFontBuffer {ArrayBuffer} Array buffer of the soundfont. This soudfont always has the id "main"
|
|
17
|
+
*/
|
|
18
|
+
constructor(initialSoundFontBuffer)
|
|
19
|
+
{
|
|
20
|
+
this.reloadManager(initialSoundFontBuffer);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
generatePresetList()
|
|
24
|
+
{
|
|
25
|
+
/**
|
|
26
|
+
* <"bank-program", "presetName">
|
|
27
|
+
* @type {Object<string, string>}
|
|
28
|
+
*/
|
|
29
|
+
const presetList = {};
|
|
30
|
+
// gather the presets in reverse and replace if necessary
|
|
31
|
+
for (let i = this.soundfontList.length - 1; i >= 0; i--)
|
|
32
|
+
{
|
|
33
|
+
const font = this.soundfontList[i];
|
|
34
|
+
/**
|
|
35
|
+
* prevent preset names from the same soudfont from being overriden
|
|
36
|
+
* if the soundfont has two presets with matching bank and program
|
|
37
|
+
* @type {Set<string>}
|
|
38
|
+
*/
|
|
39
|
+
const presets = new Set();
|
|
40
|
+
for (const p of font.soundfont.presets)
|
|
41
|
+
{
|
|
42
|
+
const presetString = `${p.bank + font.bankOffset}-${p.program}`;
|
|
43
|
+
if (presets.has(presetString))
|
|
44
|
+
{
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
presets.add(presetString);
|
|
48
|
+
presetList[presetString] = p.presetName;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @type {{bank: number, presetName: string, program: number}[]}
|
|
54
|
+
*/
|
|
55
|
+
this.presetList = [];
|
|
56
|
+
for (const [string, name] of Object.entries(presetList))
|
|
57
|
+
{
|
|
58
|
+
const pb = string.split("-");
|
|
59
|
+
this.presetList.push({
|
|
60
|
+
presetName: name,
|
|
61
|
+
program: parseInt(pb[1]),
|
|
62
|
+
bank: parseInt(pb[0])
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the final preset list
|
|
69
|
+
* @returns {{bank: number, presetName: string, program: number}[]}
|
|
70
|
+
*/
|
|
71
|
+
getPresetList()
|
|
72
|
+
{
|
|
73
|
+
return this.presetList.slice();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Clears all soundfonts and adds a new one
|
|
78
|
+
* @param soundFontArrayBuffer {ArrayBuffer}
|
|
79
|
+
*/
|
|
80
|
+
reloadManager(soundFontArrayBuffer)
|
|
81
|
+
{
|
|
82
|
+
const font = loadSoundFont(soundFontArrayBuffer);
|
|
83
|
+
/**
|
|
84
|
+
* All the soundfonts, ordered from the most important to the least.
|
|
85
|
+
* @type {SoundFontType[]}
|
|
86
|
+
*/
|
|
87
|
+
this.soundfontList = [];
|
|
88
|
+
this.soundfontList.push({
|
|
89
|
+
id: "main",
|
|
90
|
+
bankOffset: 0,
|
|
91
|
+
soundfont: font
|
|
92
|
+
});
|
|
93
|
+
this.generatePresetList();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
deleteSoundFont(id)
|
|
97
|
+
{
|
|
98
|
+
if (this.soundfontList.length === 0)
|
|
99
|
+
{
|
|
100
|
+
SpessaSynthWarn("1 soundfont left. Aborting!");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const index = this.soundfontList.findIndex(s => s.id === id);
|
|
104
|
+
if (index === -1)
|
|
105
|
+
{
|
|
106
|
+
SpessaSynthWarn(`No soundfont with id of "${id}" found. Aborting!`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
delete this.soundfontList[index].soundfont.presets;
|
|
110
|
+
delete this.soundfontList[index].soundfont.instruments;
|
|
111
|
+
delete this.soundfontList[index].soundfont.samples;
|
|
112
|
+
this.soundfontList.splice(index, 1);
|
|
113
|
+
this.generatePresetList();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Adds a new soundfont buffer with a given ID
|
|
118
|
+
* @param buffer {ArrayBuffer}
|
|
119
|
+
* @param id {string}
|
|
120
|
+
* @param bankOffset {number}
|
|
121
|
+
*/
|
|
122
|
+
addNewSoundFont(buffer, id, bankOffset)
|
|
123
|
+
{
|
|
124
|
+
if (this.soundfontList.find(s => s.id === id) !== undefined)
|
|
125
|
+
{
|
|
126
|
+
throw new Error("Cannot overwrite the existing soundfont. Use soundfontManager.delete(id) instead.");
|
|
127
|
+
}
|
|
128
|
+
this.soundfontList.push({
|
|
129
|
+
id: id,
|
|
130
|
+
soundfont: loadSoundFont(buffer),
|
|
131
|
+
bankOffset: bankOffset
|
|
132
|
+
});
|
|
133
|
+
this.generatePresetList();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Rearranges the soundfonts
|
|
138
|
+
* @param newList {string[]} the order of soundfonts, a list of strings, first overwrites second
|
|
139
|
+
*/
|
|
140
|
+
rearrangeSoundFonts(newList)
|
|
141
|
+
{
|
|
142
|
+
this.soundfontList.sort((a, b) =>
|
|
143
|
+
newList.indexOf(a.id) - newList.indexOf(b.id)
|
|
144
|
+
);
|
|
145
|
+
this.generatePresetList();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Gets a given preset from the soundfont stack
|
|
150
|
+
* @param bankNumber {number}
|
|
151
|
+
* @param programNumber {number}
|
|
152
|
+
* @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
|
|
153
|
+
* @returns {BasicPreset} the preset
|
|
154
|
+
*/
|
|
155
|
+
getPreset(bankNumber, programNumber, allowXGDrums = false)
|
|
156
|
+
{
|
|
157
|
+
if (this.soundfontList.length < 1)
|
|
158
|
+
{
|
|
159
|
+
throw new Error("No soundfonts! This should never happen.");
|
|
160
|
+
}
|
|
161
|
+
for (const sf of this.soundfontList)
|
|
162
|
+
{
|
|
163
|
+
// check for the preset (with given offset)
|
|
164
|
+
const preset = sf.soundfont.getPresetNoFallback(
|
|
165
|
+
bankNumber - sf.bankOffset,
|
|
166
|
+
programNumber,
|
|
167
|
+
allowXGDrums
|
|
168
|
+
);
|
|
169
|
+
if (preset !== undefined)
|
|
170
|
+
{
|
|
171
|
+
return preset;
|
|
172
|
+
}
|
|
173
|
+
// if not found, advance to the next soundfont
|
|
174
|
+
}
|
|
175
|
+
const isDrum = bankNumber === 128 || (allowXGDrums && isXGDrums(bankNumber));
|
|
176
|
+
// if none found, return the first correct preset found
|
|
177
|
+
if (!isDrum)
|
|
178
|
+
{
|
|
179
|
+
for (const sf of this.soundfontList)
|
|
180
|
+
{
|
|
181
|
+
const preset = sf.soundfont.presets.find(p => p.program === programNumber && !p.isDrumPreset(
|
|
182
|
+
allowXGDrums));
|
|
183
|
+
if (preset)
|
|
184
|
+
{
|
|
185
|
+
return preset;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// if nothing at all, use the first preset
|
|
189
|
+
return this.soundfontList[0].soundfont.presets[0];
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
{
|
|
193
|
+
for (const sf of this.soundfontList)
|
|
194
|
+
{
|
|
195
|
+
// check for any drum type (127/128) and matching program
|
|
196
|
+
const p = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNumber);
|
|
197
|
+
if (p)
|
|
198
|
+
{
|
|
199
|
+
return p;
|
|
200
|
+
}
|
|
201
|
+
// check for any drum preset
|
|
202
|
+
const preset = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums));
|
|
203
|
+
if (preset)
|
|
204
|
+
{
|
|
205
|
+
return preset;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// if nothing at all, use the first preset
|
|
209
|
+
return this.soundfontList[0].soundfont.presets[0];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
destroyManager()
|
|
214
|
+
{
|
|
215
|
+
this.soundfontList.forEach(s =>
|
|
216
|
+
{
|
|
217
|
+
s.soundfont.destroySoundBank();
|
|
218
|
+
});
|
|
219
|
+
delete this.soundfontList;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* stereo_panner.js
|
|
5
|
+
* purpose: pans a given voice out to the stereo output and to the effects' outputs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const PAN_SMOOTHING_FACTOR = 0.05;
|
|
9
|
+
|
|
10
|
+
export const WORKLET_SYSTEM_REVERB_DIVIDER = 4600;
|
|
11
|
+
export const WORKLET_SYSTEM_CHORUS_DIVIDER = 2000;
|
|
12
|
+
const HALF_PI = Math.PI / 2;
|
|
13
|
+
|
|
14
|
+
const MIN_PAN = -500;
|
|
15
|
+
const MAX_PAN = 500;
|
|
16
|
+
const PAN_RESOLUTION = MAX_PAN - MIN_PAN;
|
|
17
|
+
|
|
18
|
+
// initialize pan lookup tables
|
|
19
|
+
const panTableLeft = new Float32Array(PAN_RESOLUTION + 1);
|
|
20
|
+
const panTableRight = new Float32Array(PAN_RESOLUTION + 1);
|
|
21
|
+
for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
|
|
22
|
+
{
|
|
23
|
+
// clamp to 0-1
|
|
24
|
+
const realPan = (pan - MIN_PAN) / PAN_RESOLUTION;
|
|
25
|
+
const tableIndex = pan - MIN_PAN;
|
|
26
|
+
panTableLeft[tableIndex] = Math.cos(HALF_PI * realPan);
|
|
27
|
+
panTableRight[tableIndex] = Math.sin(HALF_PI * realPan);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pans the voice to the given output buffers
|
|
32
|
+
* @param voice {Voice} the voice to pan
|
|
33
|
+
* @param inputBuffer {Float32Array} the input buffer in mono
|
|
34
|
+
* @param outputLeft {Float32Array} left output buffer
|
|
35
|
+
* @param outputRight {Float32Array} right output buffer
|
|
36
|
+
* @param reverbLeft {Float32Array} left reverb input
|
|
37
|
+
* @param reverbRight {Float32Array} right reverb input
|
|
38
|
+
* @param chorusLeft {Float32Array} left chorus buffer
|
|
39
|
+
* @param chorusRight {Float32Array} right chorus buffer
|
|
40
|
+
* @this {MidiAudioChannel}
|
|
41
|
+
*/
|
|
42
|
+
export function panVoice(voice,
|
|
43
|
+
inputBuffer,
|
|
44
|
+
outputLeft, outputRight,
|
|
45
|
+
reverbLeft, reverbRight,
|
|
46
|
+
chorusLeft, chorusRight)
|
|
47
|
+
{
|
|
48
|
+
if (isNaN(inputBuffer[0]))
|
|
49
|
+
{
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* clamp -500 to 500
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
56
|
+
let pan;
|
|
57
|
+
if (voice.overridePan)
|
|
58
|
+
{
|
|
59
|
+
pan = voice.overridePan;
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
{
|
|
63
|
+
// smooth out pan to prevent clicking
|
|
64
|
+
voice.currentPan += (voice.modulatedGenerators[generatorTypes.pan] - voice.currentPan) * this.synth.panSmoothingFactor;
|
|
65
|
+
pan = voice.currentPan;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const gain = this.synth.currentGain * voice.gain;
|
|
69
|
+
const index = ~~(pan + 500);
|
|
70
|
+
// get voice's gain levels for each channel
|
|
71
|
+
const gainLeft = panTableLeft[index] * gain * this.synth.panLeft;
|
|
72
|
+
const gainRight = panTableRight[index] * gain * this.synth.panRight;
|
|
73
|
+
|
|
74
|
+
// disable reverb and chorus if necessary
|
|
75
|
+
if (this.synth.effectsEnabled)
|
|
76
|
+
{
|
|
77
|
+
const reverbSend = voice.modulatedGenerators[generatorTypes.reverbEffectsSend];
|
|
78
|
+
if (reverbSend > 0)
|
|
79
|
+
{
|
|
80
|
+
// reverb is mono so we need to multiply by gain
|
|
81
|
+
const reverbGain = this.synth.reverbGain * gain * (reverbSend / WORKLET_SYSTEM_REVERB_DIVIDER);
|
|
82
|
+
for (let i = 0; i < inputBuffer.length; i++)
|
|
83
|
+
{
|
|
84
|
+
reverbLeft[i] += reverbGain * inputBuffer[i];
|
|
85
|
+
}
|
|
86
|
+
// copy as its mono
|
|
87
|
+
reverbRight.set(reverbLeft);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const chorusSend = voice.modulatedGenerators[generatorTypes.chorusEffectsSend];
|
|
91
|
+
if (chorusSend > 0)
|
|
92
|
+
{
|
|
93
|
+
// chorus is stereo so we do not need to
|
|
94
|
+
const chorusGain = this.synth.chorusGain * chorusSend / WORKLET_SYSTEM_CHORUS_DIVIDER;
|
|
95
|
+
const chorusLeftGain = gainLeft * chorusGain;
|
|
96
|
+
const chorusRightGain = gainRight * chorusGain;
|
|
97
|
+
for (let i = 0; i < inputBuffer.length; i++)
|
|
98
|
+
{
|
|
99
|
+
chorusLeft[i] += chorusLeftGain * inputBuffer[i];
|
|
100
|
+
chorusRight[i] += chorusRightGain * inputBuffer[i];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// mix down the audio data
|
|
106
|
+
if (gainLeft > 0)
|
|
107
|
+
{
|
|
108
|
+
for (let i = 0; i < inputBuffer.length; i++)
|
|
109
|
+
{
|
|
110
|
+
outputLeft[i] += gainLeft * inputBuffer[i];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (gainRight > 0)
|
|
114
|
+
{
|
|
115
|
+
for (let i = 0; i < inputBuffer.length; i++)
|
|
116
|
+
{
|
|
117
|
+
outputRight[i] += gainRight * inputBuffer[i];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
const MIN_TIMECENT = -15000;
|
|
9
9
|
const MAX_TIMECENT = 15000;
|
|
10
10
|
const timecentLookupTable = new Float32Array(MAX_TIMECENT - MIN_TIMECENT + 1);
|
|
11
|
-
for (let i = 0; i < timecentLookupTable.length; i++)
|
|
11
|
+
for (let i = 0; i < timecentLookupTable.length; i++)
|
|
12
|
+
{
|
|
12
13
|
const timecents = MIN_TIMECENT + i;
|
|
13
14
|
timecentLookupTable[i] = Math.pow(2, timecents / 1200);
|
|
14
15
|
}
|
|
@@ -20,6 +21,10 @@ for (let i = 0; i < timecentLookupTable.length; i++) {
|
|
|
20
21
|
*/
|
|
21
22
|
export function timecentsToSeconds(timecents)
|
|
22
23
|
{
|
|
24
|
+
if (timecents <= -32767)
|
|
25
|
+
{
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
23
28
|
return timecentLookupTable[timecents - MIN_TIMECENT];
|
|
24
29
|
}
|
|
25
30
|
|
|
@@ -27,7 +32,8 @@ export function timecentsToSeconds(timecents)
|
|
|
27
32
|
const MIN_ABS_CENT = -20000; // freqVibLfo
|
|
28
33
|
const MAX_ABS_CENT = 16500; // filterFc
|
|
29
34
|
const absoluteCentLookupTable = new Float32Array(MAX_ABS_CENT - MIN_ABS_CENT + 1);
|
|
30
|
-
for (let i = 0; i < absoluteCentLookupTable.length; i++)
|
|
35
|
+
for (let i = 0; i < absoluteCentLookupTable.length; i++)
|
|
36
|
+
{
|
|
31
37
|
const absoluteCents = MIN_ABS_CENT + i;
|
|
32
38
|
absoluteCentLookupTable[i] = 440 * Math.pow(2, (absoluteCents - 6900) / 1200);
|
|
33
39
|
}
|
|
@@ -39,7 +45,7 @@ for (let i = 0; i < absoluteCentLookupTable.length; i++) {
|
|
|
39
45
|
*/
|
|
40
46
|
export function absCentsToHz(cents)
|
|
41
47
|
{
|
|
42
|
-
if(cents < MIN_ABS_CENT || cents > MAX_ABS_CENT)
|
|
48
|
+
if (cents < MIN_ABS_CENT || cents > MAX_ABS_CENT)
|
|
43
49
|
{
|
|
44
50
|
return 440 * Math.pow(2, (cents - 6900) / 1200);
|
|
45
51
|
}
|
|
@@ -50,7 +56,8 @@ export function absCentsToHz(cents)
|
|
|
50
56
|
const MIN_DECIBELS = -1660;
|
|
51
57
|
const MAX_DECIBELS = 1600;
|
|
52
58
|
const decibelLookUpTable = new Float32Array((MAX_DECIBELS - MIN_DECIBELS) * 100 + 1);
|
|
53
|
-
for (let i = 0; i < decibelLookUpTable.length; i++)
|
|
59
|
+
for (let i = 0; i < decibelLookUpTable.length; i++)
|
|
60
|
+
{
|
|
54
61
|
const decibels = (MIN_DECIBELS * 100 + i) / 100;
|
|
55
62
|
decibelLookUpTable[i] = Math.pow(10, -decibels / 20);
|
|
56
63
|
}
|