spessasynth_core 3.27.7 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -116
- package/dist/index.d.ts +4057 -0
- package/dist/index.js +17188 -0
- package/dist/index.js.map +1 -0
- package/package.json +23 -6
- package/index.js +0 -132
- package/src/externals/README.md +0 -6
- package/src/externals/fflate/LICENSE +0 -21
- package/src/externals/fflate/fflate.min.js +0 -1
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -12
- package/src/externals/stbvorbis_sync/LICENSE +0 -202
- package/src/externals/stbvorbis_sync/NOTICE +0 -6
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -1
- package/src/midi/README.md +0 -32
- package/src/midi/basic_midi.js +0 -587
- package/src/midi/midi_builder.js +0 -203
- package/src/midi/midi_loader.js +0 -321
- package/src/midi/midi_message.js +0 -254
- package/src/midi/midi_sequence.js +0 -230
- package/src/midi/midi_tools/get_note_times.js +0 -154
- package/src/midi/midi_tools/midi_editor.js +0 -611
- package/src/midi/midi_tools/midi_writer.js +0 -105
- package/src/midi/midi_tools/rmidi_writer.js +0 -566
- package/src/midi/midi_tools/used_keys_loaded.js +0 -256
- package/src/midi/xmf_loader.js +0 -454
- package/src/sequencer/README.md +0 -9
- package/src/sequencer/events.js +0 -81
- package/src/sequencer/play.js +0 -362
- package/src/sequencer/process_event.js +0 -165
- package/src/sequencer/process_tick.js +0 -104
- package/src/sequencer/sequencer_engine.js +0 -372
- package/src/sequencer/song_control.js +0 -196
- package/src/soundfont/README.md +0 -11
- package/src/soundfont/basic_soundfont/basic_global_zone.js +0 -6
- package/src/soundfont/basic_soundfont/basic_instrument.js +0 -115
- package/src/soundfont/basic_soundfont/basic_instrument_zone.js +0 -45
- package/src/soundfont/basic_soundfont/basic_preset.js +0 -313
- package/src/soundfont/basic_soundfont/basic_preset_zone.js +0 -39
- package/src/soundfont/basic_soundfont/basic_sample.js +0 -477
- package/src/soundfont/basic_soundfont/basic_soundbank.js +0 -740
- package/src/soundfont/basic_soundfont/basic_zone.js +0 -145
- package/src/soundfont/basic_soundfont/generator.js +0 -76
- package/src/soundfont/basic_soundfont/generator_types.js +0 -151
- package/src/soundfont/basic_soundfont/modulator.js +0 -581
- package/src/soundfont/basic_soundfont/riff_chunk.js +0 -195
- package/src/soundfont/basic_soundfont/write_dls/art2.js +0 -174
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +0 -49
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +0 -374
- package/src/soundfont/basic_soundfont/write_dls/ins.js +0 -85
- package/src/soundfont/basic_soundfont/write_dls/lins.js +0 -15
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +0 -330
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +0 -120
- package/src/soundfont/basic_soundfont/write_dls/wave.js +0 -71
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +0 -124
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +0 -78
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +0 -35
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +0 -60
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +0 -91
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +0 -62
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +0 -42
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +0 -57
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +0 -92
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +0 -61
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +0 -62
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +0 -131
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +0 -77
- package/src/soundfont/basic_soundfont/write_sf2/write.js +0 -287
- package/src/soundfont/dls/articulator_converter.js +0 -402
- package/src/soundfont/dls/dls_destinations.js +0 -38
- package/src/soundfont/dls/dls_instrument.js +0 -20
- package/src/soundfont/dls/dls_preset.js +0 -43
- package/src/soundfont/dls/dls_sample.js +0 -238
- package/src/soundfont/dls/dls_soundfont.js +0 -183
- package/src/soundfont/dls/dls_sources.js +0 -63
- package/src/soundfont/dls/dls_zone.js +0 -89
- package/src/soundfont/dls/read_articulation.js +0 -300
- package/src/soundfont/dls/read_instrument.js +0 -118
- package/src/soundfont/dls/read_instrument_list.js +0 -17
- package/src/soundfont/dls/read_lart.js +0 -35
- package/src/soundfont/dls/read_region.js +0 -157
- package/src/soundfont/dls/read_samples.js +0 -154
- package/src/soundfont/load_soundfont.js +0 -21
- package/src/soundfont/read_sf2/generators.js +0 -43
- package/src/soundfont/read_sf2/instrument_zones.js +0 -75
- package/src/soundfont/read_sf2/instruments.js +0 -71
- package/src/soundfont/read_sf2/modulators.js +0 -25
- package/src/soundfont/read_sf2/preset_zones.js +0 -79
- package/src/soundfont/read_sf2/presets.js +0 -80
- package/src/soundfont/read_sf2/samples.js +0 -317
- package/src/soundfont/read_sf2/soundfont.js +0 -452
- package/src/soundfont/read_sf2/zones.js +0 -28
- package/src/synthetizer/README.md +0 -7
- package/src/synthetizer/audio_engine/README.md +0 -9
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +0 -289
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +0 -90
- package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +0 -95
- package/src/synthetizer/audio_engine/engine_components/enums.js +0 -18
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +0 -151
- package/src/synthetizer/audio_engine/engine_components/lfo.js +0 -26
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +0 -282
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +0 -551
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +0 -181
- package/src/synthetizer/audio_engine/engine_components/modulator_curves.js +0 -89
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +0 -265
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +0 -124
- package/src/synthetizer/audio_engine/engine_components/unit_converter.js +0 -73
- package/src/synthetizer/audio_engine/engine_components/voice.js +0 -525
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +0 -402
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +0 -274
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +0 -159
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +0 -48
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +0 -254
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +0 -20
- package/src/synthetizer/audio_engine/engine_methods/data_entry/awe32.js +0 -198
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +0 -281
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +0 -109
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +0 -17
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +0 -214
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +0 -92
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +0 -35
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +0 -214
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/embedded_sound_bank.js +0 -42
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/update_preset_list.js +0 -19
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +0 -23
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +0 -56
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +0 -16
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +0 -30
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +0 -63
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +0 -1058
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +0 -23
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +0 -31
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +0 -29
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +0 -15
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +0 -27
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +0 -19
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +0 -27
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +0 -15
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +0 -34
- package/src/synthetizer/audio_engine/main_processor.js +0 -813
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +0 -16
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +0 -175
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +0 -116
- package/src/synthetizer/audio_engine/synth_processor_options.js +0 -18
- package/src/synthetizer/synth_constants.js +0 -26
- package/src/utils/README.md +0 -8
- package/src/utils/buffer_to_wav.js +0 -197
- package/src/utils/byte_functions/big_endian.js +0 -32
- package/src/utils/byte_functions/little_endian.js +0 -77
- package/src/utils/byte_functions/string.js +0 -92
- package/src/utils/byte_functions/variable_length_quantity.js +0 -42
- package/src/utils/fill_with_defaults.js +0 -21
- package/src/utils/indexed_array.js +0 -34
- package/src/utils/loggin.js +0 -71
- package/src/utils/other.js +0 -92
- package/src/utils/sysex_detector.js +0 -58
- package/src/utils/xg_hacks.js +0 -193
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { timecentsToSeconds } from "./unit_converter.js";
|
|
2
|
-
import { getModulatorCurveValue } from "./modulator_curves.js";
|
|
3
|
-
import { modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
4
|
-
import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_types.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* modulation_envelope.js
|
|
8
|
-
* purpose: calculates the modulation envelope for the given voice
|
|
9
|
-
*/
|
|
10
|
-
const MODENV_PEAK = 1;
|
|
11
|
-
|
|
12
|
-
// 1000 should be precise enough
|
|
13
|
-
const CONVEX_ATTACK = new Float32Array(1000);
|
|
14
|
-
for (let i = 0; i < CONVEX_ATTACK.length; i++)
|
|
15
|
-
{
|
|
16
|
-
// this makes the db linear (I think)
|
|
17
|
-
CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class ModulationEnvelope
|
|
21
|
-
{
|
|
22
|
-
/**
|
|
23
|
-
* The attack duration, in seconds
|
|
24
|
-
* @type {number}
|
|
25
|
-
*/
|
|
26
|
-
attackDuration = 0;
|
|
27
|
-
/**
|
|
28
|
-
* The decay duration, in seconds
|
|
29
|
-
* @type {number}
|
|
30
|
-
*/
|
|
31
|
-
decayDuration = 0;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The hold duration, in seconds
|
|
35
|
-
* @type {number}
|
|
36
|
-
*/
|
|
37
|
-
holdDuration = 0;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Release duration, in seconds
|
|
41
|
-
* @type {number}
|
|
42
|
-
*/
|
|
43
|
-
releaseDuration = 0;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The sustain level 0-1
|
|
47
|
-
* @type {number}
|
|
48
|
-
*/
|
|
49
|
-
sustainLevel = 0;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Delay phase end time in seconds, absolute (audio context time)
|
|
53
|
-
* @type {number}
|
|
54
|
-
*/
|
|
55
|
-
delayEnd = 0;
|
|
56
|
-
/**
|
|
57
|
-
* Attack phase end time in seconds, absolute (audio context time)
|
|
58
|
-
* @type {number}
|
|
59
|
-
*/
|
|
60
|
-
attackEnd = 0;
|
|
61
|
-
/**
|
|
62
|
-
* Hold phase end time in seconds, absolute (audio context time)
|
|
63
|
-
* @type {number}
|
|
64
|
-
*/
|
|
65
|
-
holdEnd = 0;
|
|
66
|
-
/**
|
|
67
|
-
* Decay phase end time in seconds, absolute (audio context time)
|
|
68
|
-
* @type {number}
|
|
69
|
-
*/
|
|
70
|
-
decayEnd = 0;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* The level of the envelope when the release phase starts
|
|
74
|
-
* @type {number}
|
|
75
|
-
*/
|
|
76
|
-
releaseStartLevel = 0;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The current modulation envelope value
|
|
80
|
-
* @type {number}
|
|
81
|
-
*/
|
|
82
|
-
currentValue = 0;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Starts the release phase in the envelope
|
|
86
|
-
* @param voice {Voice} the voice this envelope belongs to
|
|
87
|
-
*/
|
|
88
|
-
static startRelease(voice)
|
|
89
|
-
{
|
|
90
|
-
ModulationEnvelope.recalculate(voice);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param voice {Voice} the voice to recalculate
|
|
95
|
-
*/
|
|
96
|
-
static recalculate(voice)
|
|
97
|
-
{
|
|
98
|
-
const env = voice.modulationEnvelope;
|
|
99
|
-
|
|
100
|
-
// in release? Might need to recalculate the value as it can be modulated
|
|
101
|
-
if (voice.isInRelease)
|
|
102
|
-
{
|
|
103
|
-
env.releaseStartLevel = ModulationEnvelope.getValue(voice, voice.releaseStartTime, true);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
env.sustainLevel = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
|
|
107
|
-
|
|
108
|
-
env.attackDuration = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
|
|
109
|
-
|
|
110
|
-
const decayKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]);
|
|
111
|
-
const decayTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents);
|
|
112
|
-
// according to the specification, the decay time is the time it takes to reach 0% from 100%.
|
|
113
|
-
// calculate the time to reach actual sustain level,
|
|
114
|
-
// for example, sustain 0.6 will be 0.4 of the decay time
|
|
115
|
-
env.decayDuration = decayTime * (1 - env.sustainLevel);
|
|
116
|
-
|
|
117
|
-
const holdKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]);
|
|
118
|
-
env.holdDuration = timecentsToSeconds(holdKeyExcursionCents + voice.modulatedGenerators[generatorTypes.holdModEnv]);
|
|
119
|
-
|
|
120
|
-
const releaseTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
|
|
121
|
-
// release time is from the full level to 0%
|
|
122
|
-
// to get the actual time, multiply by the release start level
|
|
123
|
-
env.releaseDuration = releaseTime * env.releaseStartLevel;
|
|
124
|
-
|
|
125
|
-
env.delayEnd = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]);
|
|
126
|
-
env.attackEnd = env.delayEnd + env.attackDuration;
|
|
127
|
-
env.holdEnd = env.attackEnd + env.holdDuration;
|
|
128
|
-
env.decayEnd = env.holdEnd + env.decayDuration;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Calculates the current modulation envelope value for the given time and voice
|
|
133
|
-
* @param voice {Voice} the voice we are working on
|
|
134
|
-
* @param currentTime {number} in seconds
|
|
135
|
-
* @param ignoreRelease {boolean} if true, it will compute the value as if the voice was not released
|
|
136
|
-
* @returns {number} modenv value, from 0 to 1
|
|
137
|
-
*/
|
|
138
|
-
static getValue(voice, currentTime, ignoreRelease = false)
|
|
139
|
-
{
|
|
140
|
-
const env = voice.modulationEnvelope;
|
|
141
|
-
if (voice.isInRelease && !ignoreRelease)
|
|
142
|
-
{
|
|
143
|
-
// if the voice is still in the delay phase,
|
|
144
|
-
// start level will be 0 that will result in divide by zero
|
|
145
|
-
if (env.releaseStartLevel === 0)
|
|
146
|
-
{
|
|
147
|
-
return 0;
|
|
148
|
-
}
|
|
149
|
-
return Math.max(
|
|
150
|
-
0,
|
|
151
|
-
(1 - (currentTime - voice.releaseStartTime) / env.releaseDuration) * env.releaseStartLevel
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (currentTime < env.delayEnd)
|
|
156
|
-
{
|
|
157
|
-
env.currentValue = 0; // delay
|
|
158
|
-
}
|
|
159
|
-
else if (currentTime < env.attackEnd)
|
|
160
|
-
{
|
|
161
|
-
// modulation envelope uses convex curve for attack
|
|
162
|
-
env.currentValue = CONVEX_ATTACK[~~((1 - (env.attackEnd - currentTime) / env.attackDuration) * 1000)];
|
|
163
|
-
}
|
|
164
|
-
else if (currentTime < env.holdEnd)
|
|
165
|
-
{
|
|
166
|
-
// hold: stay at 1
|
|
167
|
-
env.currentValue = MODENV_PEAK;
|
|
168
|
-
}
|
|
169
|
-
else if (currentTime < env.decayEnd)
|
|
170
|
-
{
|
|
171
|
-
// decay: linear ramp from 1 to sustain level
|
|
172
|
-
env.currentValue = (1 - (env.decayEnd - currentTime) / env.decayDuration) * (env.sustainLevel - MODENV_PEAK) + MODENV_PEAK;
|
|
173
|
-
}
|
|
174
|
-
else
|
|
175
|
-
{
|
|
176
|
-
// sustain: stay at sustain level
|
|
177
|
-
env.currentValue = env.sustainLevel;
|
|
178
|
-
}
|
|
179
|
-
return env.currentValue;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* modulator_curves.js
|
|
5
|
-
* precomputes modulator concave and conves curves and calculates a curve value for a given polarity, direction and type
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// the length of the precomputed curve tables
|
|
9
|
-
export const MOD_PRECOMPUTED_LENGTH = 16384;
|
|
10
|
-
|
|
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
|
-
// the equation is taken from FluidSynth as it's the standard for soundFonts
|
|
15
|
-
// more precisely, the gen_conv.c file
|
|
16
|
-
concave[0] = 0;
|
|
17
|
-
concave[concave.length - 1] = 1;
|
|
18
|
-
|
|
19
|
-
convex[0] = 0;
|
|
20
|
-
convex[convex.length - 1] = 1;
|
|
21
|
-
for (let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
|
|
22
|
-
{
|
|
23
|
-
let x = (-200 * 2 / 960) * Math.log(i / (concave.length - 1)) / Math.LN10;
|
|
24
|
-
convex[i] = 1 - x;
|
|
25
|
-
concave[concave.length - 1 - i] = x;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Transforms a value with a given curve type
|
|
30
|
-
* @param polarity {number} 0 or 1
|
|
31
|
-
* @param direction {number} 0 or 1
|
|
32
|
-
* @param curveType {number} see modulatorCurveTypes in modulators.js
|
|
33
|
-
* @param value {number} the linear value, 0 to 1
|
|
34
|
-
* @returns {number} the transformed value, 0 to 1, or -1 to 1
|
|
35
|
-
*/
|
|
36
|
-
export function getModulatorCurveValue(direction, curveType, value, polarity)
|
|
37
|
-
{
|
|
38
|
-
// inverse the value if needed
|
|
39
|
-
if (direction)
|
|
40
|
-
{
|
|
41
|
-
value = 1 - value;
|
|
42
|
-
}
|
|
43
|
-
switch (curveType)
|
|
44
|
-
{
|
|
45
|
-
case modulatorCurveTypes.linear:
|
|
46
|
-
if (polarity)
|
|
47
|
-
{
|
|
48
|
-
// bipolar curve
|
|
49
|
-
return value * 2 - 1;
|
|
50
|
-
}
|
|
51
|
-
return value;
|
|
52
|
-
|
|
53
|
-
case modulatorCurveTypes.switch:
|
|
54
|
-
// switch
|
|
55
|
-
value = value > 0.5 ? 1 : 0;
|
|
56
|
-
if (polarity)
|
|
57
|
-
{
|
|
58
|
-
// multiply
|
|
59
|
-
return value * 2 - 1;
|
|
60
|
-
}
|
|
61
|
-
return value;
|
|
62
|
-
|
|
63
|
-
case modulatorCurveTypes.concave:
|
|
64
|
-
// look up the value
|
|
65
|
-
if (polarity)
|
|
66
|
-
{
|
|
67
|
-
value = value * 2 - 1;
|
|
68
|
-
if (value < 0)
|
|
69
|
-
{
|
|
70
|
-
return -concave[~~(value * -MOD_PRECOMPUTED_LENGTH)];
|
|
71
|
-
}
|
|
72
|
-
return concave[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
73
|
-
}
|
|
74
|
-
return concave[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
75
|
-
|
|
76
|
-
case modulatorCurveTypes.convex:
|
|
77
|
-
// look up the value
|
|
78
|
-
if (polarity)
|
|
79
|
-
{
|
|
80
|
-
value = value * 2 - 1;
|
|
81
|
-
if (value < 0)
|
|
82
|
-
{
|
|
83
|
-
return -convex[~~(value * -MOD_PRECOMPUTED_LENGTH)];
|
|
84
|
-
}
|
|
85
|
-
return convex[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
86
|
-
}
|
|
87
|
-
return convex[~~(value * MOD_PRECOMPUTED_LENGTH)];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
import { SpessaSynthInfo, SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
2
|
-
import { isXGDrums } from "../../../utils/xg_hacks.js";
|
|
3
|
-
import { EMBEDDED_SOUND_BANK_ID } from "../../synth_constants.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 SoundFontManager
|
|
13
|
-
{
|
|
14
|
-
/**
|
|
15
|
-
* All the soundfonts, ordered from the most important to the least.
|
|
16
|
-
* @type {SoundFontType[]}
|
|
17
|
-
*/
|
|
18
|
-
soundfontList = [];
|
|
19
|
-
/**
|
|
20
|
-
* @type {{bank: number, presetName: string, program: number}[]}
|
|
21
|
-
*/
|
|
22
|
-
presetList = [];
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param presetListChangeCallback {function} to call when stuff changes
|
|
26
|
-
*/
|
|
27
|
-
constructor(presetListChangeCallback)
|
|
28
|
-
{
|
|
29
|
-
this.presetListChangeCallback = presetListChangeCallback;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
generatePresetList()
|
|
33
|
-
{
|
|
34
|
-
/**
|
|
35
|
-
* <"bank-program", "presetName">
|
|
36
|
-
* @type {Record<string, string>}
|
|
37
|
-
*/
|
|
38
|
-
const presetList = {};
|
|
39
|
-
// gather the presets in reverse and replace if necessary
|
|
40
|
-
for (let i = this.soundfontList.length - 1; i >= 0; i--)
|
|
41
|
-
{
|
|
42
|
-
const font = this.soundfontList[i];
|
|
43
|
-
/**
|
|
44
|
-
* prevent preset names from the same soudfont from being overriden
|
|
45
|
-
* if the soundfont has two presets with matching bank and program
|
|
46
|
-
* @type {Set<string>}
|
|
47
|
-
*/
|
|
48
|
-
const presets = new Set();
|
|
49
|
-
for (const p of font.soundfont.presets)
|
|
50
|
-
{
|
|
51
|
-
const bank = Math.min(128, p.bank + font.bankOffset);
|
|
52
|
-
const presetString = `${bank}-${p.program}`;
|
|
53
|
-
if (presets.has(presetString))
|
|
54
|
-
{
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
presets.add(presetString);
|
|
58
|
-
presetList[presetString] = p.presetName;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.presetList = [];
|
|
63
|
-
for (const [string, name] of Object.entries(presetList))
|
|
64
|
-
{
|
|
65
|
-
const pb = string.split("-");
|
|
66
|
-
this.presetList.push({
|
|
67
|
-
presetName: name,
|
|
68
|
-
program: parseInt(pb[1]),
|
|
69
|
-
bank: parseInt(pb[0])
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
this.presetListChangeCallback();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get the final preset list
|
|
77
|
-
* @returns {{bank: number, presetName: string, program: number}[]}
|
|
78
|
-
*/
|
|
79
|
-
getPresetList()
|
|
80
|
-
{
|
|
81
|
-
return this.presetList.slice();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// noinspection JSUnusedGlobalSymbols
|
|
85
|
-
/**
|
|
86
|
-
* Clears all soundfonts and adds a new one with an ID "main"
|
|
87
|
-
* @param soundFont {BasicSoundBank}
|
|
88
|
-
*/
|
|
89
|
-
reloadManager(soundFont)
|
|
90
|
-
{
|
|
91
|
-
// do not clear the embedded bank
|
|
92
|
-
this.soundfontList = this.soundfontList.filter(sf => sf.id === EMBEDDED_SOUND_BANK_ID);
|
|
93
|
-
this.soundfontList.push({
|
|
94
|
-
id: "main",
|
|
95
|
-
bankOffset: 0,
|
|
96
|
-
soundfont: soundFont
|
|
97
|
-
});
|
|
98
|
-
this.generatePresetList();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// noinspection JSUnusedGlobalSymbols
|
|
102
|
-
/**
|
|
103
|
-
* Deletes a given soundfont.
|
|
104
|
-
* @param id {string}
|
|
105
|
-
*/
|
|
106
|
-
deleteSoundFont(id)
|
|
107
|
-
{
|
|
108
|
-
if (this.soundfontList.length === 0)
|
|
109
|
-
{
|
|
110
|
-
SpessaSynthWarn("1 soundfont left. Aborting!");
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const index = this.soundfontList.findIndex(s => s.id === id);
|
|
114
|
-
if (index === -1)
|
|
115
|
-
{
|
|
116
|
-
SpessaSynthInfo(`No soundfont with id of "${id}" found. Aborting!`);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.soundfontList.splice(index, 1);
|
|
120
|
-
this.generatePresetList();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// noinspection JSUnusedGlobalSymbols
|
|
124
|
-
/**
|
|
125
|
-
* Adds a new soundfont with a given ID, or replaces an existing one.
|
|
126
|
-
* @param font {BasicSoundBank}
|
|
127
|
-
* @param id {string}
|
|
128
|
-
* @param bankOffset {number}
|
|
129
|
-
*/
|
|
130
|
-
addNewSoundFont(font, id, bankOffset)
|
|
131
|
-
{
|
|
132
|
-
if (this.soundfontList.find(s => s.id === id) !== undefined)
|
|
133
|
-
{
|
|
134
|
-
// replace
|
|
135
|
-
const soundfont = this.soundfontList.find(s => s.id === id);
|
|
136
|
-
soundfont.soundfont = font;
|
|
137
|
-
soundfont.bankOffset = bankOffset;
|
|
138
|
-
}
|
|
139
|
-
else
|
|
140
|
-
{
|
|
141
|
-
this.soundfontList.push({
|
|
142
|
-
id: id,
|
|
143
|
-
soundfont: font,
|
|
144
|
-
bankOffset: bankOffset
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
this.generatePresetList();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Gets the current soundfont order
|
|
152
|
-
* @returns {string[]}
|
|
153
|
-
*/
|
|
154
|
-
getCurrentSoundFontOrder()
|
|
155
|
-
{
|
|
156
|
-
return this.soundfontList.map(s => s.id);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// noinspection JSUnusedGlobalSymbols
|
|
160
|
-
/**
|
|
161
|
-
* Rearranges the soundfonts
|
|
162
|
-
* @param newList {string[]} the order of soundfonts, a list of strings, first overwrites second
|
|
163
|
-
*/
|
|
164
|
-
rearrangeSoundFonts(newList)
|
|
165
|
-
{
|
|
166
|
-
this.soundfontList.sort((a, b) =>
|
|
167
|
-
newList.indexOf(a.id) - newList.indexOf(b.id)
|
|
168
|
-
);
|
|
169
|
-
this.generatePresetList();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Gets a given preset from the soundfont stack
|
|
174
|
-
* @param bankNumber {number}
|
|
175
|
-
* @param programNumber {number}
|
|
176
|
-
* @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
|
|
177
|
-
* @returns {{preset: BasicPreset, bankOffset: number}} the preset and its bank offset
|
|
178
|
-
*/
|
|
179
|
-
getPreset(bankNumber, programNumber, allowXGDrums = false)
|
|
180
|
-
{
|
|
181
|
-
if (this.soundfontList.length < 1)
|
|
182
|
-
{
|
|
183
|
-
throw new Error("No soundfonts! Did you forget to add one?");
|
|
184
|
-
}
|
|
185
|
-
const isDrum = bankNumber === 128 || (allowXGDrums && isXGDrums(bankNumber));
|
|
186
|
-
for (const sf of this.soundfontList)
|
|
187
|
-
{
|
|
188
|
-
// check for the preset (with given offset)
|
|
189
|
-
const preset = sf.soundfont.getPresetNoFallback(
|
|
190
|
-
bankNumber === 128 ? 128 : bankNumber - sf.bankOffset,
|
|
191
|
-
programNumber,
|
|
192
|
-
allowXGDrums
|
|
193
|
-
);
|
|
194
|
-
if (preset !== undefined)
|
|
195
|
-
{
|
|
196
|
-
return {
|
|
197
|
-
preset: preset,
|
|
198
|
-
bankOffset: sf.bankOffset
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
// if not found, advance to the next soundfont
|
|
202
|
-
}
|
|
203
|
-
// if none found, return the first correct preset found
|
|
204
|
-
if (!isDrum)
|
|
205
|
-
{
|
|
206
|
-
for (const sf of this.soundfontList)
|
|
207
|
-
{
|
|
208
|
-
const preset = sf.soundfont.presets.find(p => p.program === programNumber && !p.isDrumPreset(
|
|
209
|
-
allowXGDrums));
|
|
210
|
-
if (preset)
|
|
211
|
-
{
|
|
212
|
-
return {
|
|
213
|
-
preset: preset,
|
|
214
|
-
bankOffset: sf.bankOffset
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// if nothing at all, use the first preset
|
|
219
|
-
const sf = this.soundfontList[0];
|
|
220
|
-
return {
|
|
221
|
-
preset: sf.soundfont.presets[0],
|
|
222
|
-
bankOffset: sf.bankOffset
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
else
|
|
226
|
-
{
|
|
227
|
-
for (const sf of this.soundfontList)
|
|
228
|
-
{
|
|
229
|
-
// check for any drum type (127/128) and matching program
|
|
230
|
-
const p = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNumber);
|
|
231
|
-
if (p)
|
|
232
|
-
{
|
|
233
|
-
return {
|
|
234
|
-
preset: p,
|
|
235
|
-
bankOffset: sf.bankOffset
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
// check for any drum preset
|
|
239
|
-
const preset = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums));
|
|
240
|
-
if (preset)
|
|
241
|
-
{
|
|
242
|
-
return {
|
|
243
|
-
preset: preset,
|
|
244
|
-
bankOffset: sf.bankOffset
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
// if nothing at all, use the first preset
|
|
249
|
-
const sf = this.soundfontList[0];
|
|
250
|
-
return {
|
|
251
|
-
preset: sf.soundfont.presets[0],
|
|
252
|
-
bankOffset: sf.bankOffset
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
destroyManager()
|
|
258
|
-
{
|
|
259
|
-
this.soundfontList.forEach(s =>
|
|
260
|
-
{
|
|
261
|
-
s.soundfont.destroySoundBank();
|
|
262
|
-
});
|
|
263
|
-
delete this.soundfontList;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_types.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
|
-
// optimized for spessasynth_lib's effects
|
|
11
|
-
export const REVERB_DIVIDER = 3070;
|
|
12
|
-
export const CHORUS_DIVIDER = 2000;
|
|
13
|
-
const HALF_PI = Math.PI / 2;
|
|
14
|
-
|
|
15
|
-
const MIN_PAN = -500;
|
|
16
|
-
const MAX_PAN = 500;
|
|
17
|
-
const PAN_RESOLUTION = MAX_PAN - MIN_PAN;
|
|
18
|
-
|
|
19
|
-
// initialize pan lookup tables
|
|
20
|
-
const panTableLeft = new Float32Array(PAN_RESOLUTION + 1);
|
|
21
|
-
const panTableRight = new Float32Array(PAN_RESOLUTION + 1);
|
|
22
|
-
for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
|
|
23
|
-
{
|
|
24
|
-
// clamp to 0-1
|
|
25
|
-
const realPan = (pan - MIN_PAN) / PAN_RESOLUTION;
|
|
26
|
-
const tableIndex = pan - MIN_PAN;
|
|
27
|
-
panTableLeft[tableIndex] = Math.cos(HALF_PI * realPan);
|
|
28
|
-
panTableRight[tableIndex] = Math.sin(HALF_PI * realPan);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Pans the voice to the given output buffers
|
|
33
|
-
* @param voice {Voice} the voice to pan
|
|
34
|
-
* @param inputBuffer {Float32Array} the input buffer in mono
|
|
35
|
-
* @param outputLeft {Float32Array} left output buffer
|
|
36
|
-
* @param outputRight {Float32Array} right output buffer
|
|
37
|
-
* @param reverbLeft {Float32Array} left reverb input
|
|
38
|
-
* @param reverbRight {Float32Array} right reverb input
|
|
39
|
-
* @param chorusLeft {Float32Array} left chorus buffer
|
|
40
|
-
* @param chorusRight {Float32Array} right chorus buffer
|
|
41
|
-
* @param startIndex {number}
|
|
42
|
-
* @this {MidiAudioChannel}
|
|
43
|
-
*/
|
|
44
|
-
export function panAndMixVoice(voice,
|
|
45
|
-
inputBuffer,
|
|
46
|
-
outputLeft, outputRight,
|
|
47
|
-
reverbLeft, reverbRight,
|
|
48
|
-
chorusLeft, chorusRight,
|
|
49
|
-
startIndex)
|
|
50
|
-
{
|
|
51
|
-
if (isNaN(inputBuffer[0]))
|
|
52
|
-
{
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* clamp -500 to 500
|
|
57
|
-
* @type {number}
|
|
58
|
-
*/
|
|
59
|
-
let pan;
|
|
60
|
-
if (voice.overridePan)
|
|
61
|
-
{
|
|
62
|
-
pan = voice.overridePan;
|
|
63
|
-
}
|
|
64
|
-
else
|
|
65
|
-
{
|
|
66
|
-
// smooth out pan to prevent clicking
|
|
67
|
-
voice.currentPan += (voice.modulatedGenerators[generatorTypes.pan] - voice.currentPan) * this.synth.panSmoothingFactor;
|
|
68
|
-
pan = voice.currentPan;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const gain = this.synth.currentGain * voice.gain;
|
|
72
|
-
const index = ~~(pan + 500);
|
|
73
|
-
// get voice's gain levels for each channel
|
|
74
|
-
const gainLeft = panTableLeft[index] * gain * this.synth.panLeft;
|
|
75
|
-
const gainRight = panTableRight[index] * gain * this.synth.panRight;
|
|
76
|
-
|
|
77
|
-
// disable reverb and chorus if necessary
|
|
78
|
-
if (this.synth.effectsEnabled)
|
|
79
|
-
{
|
|
80
|
-
const reverbSend = voice.modulatedGenerators[generatorTypes.reverbEffectsSend];
|
|
81
|
-
if (reverbSend > 0)
|
|
82
|
-
{
|
|
83
|
-
// reverb is mono so we need to multiply by gain
|
|
84
|
-
const reverbGain = this.synth.reverbGain * this.synth.reverbSend * gain * (reverbSend / REVERB_DIVIDER);
|
|
85
|
-
for (let i = 0; i < inputBuffer.length; i++)
|
|
86
|
-
{
|
|
87
|
-
const idx = i + startIndex;
|
|
88
|
-
reverbLeft[idx] += reverbGain * inputBuffer[i];
|
|
89
|
-
reverbRight[idx] += reverbGain * inputBuffer[i];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const chorusSend = voice.modulatedGenerators[generatorTypes.chorusEffectsSend];
|
|
94
|
-
if (chorusSend > 0)
|
|
95
|
-
{
|
|
96
|
-
// chorus is stereo so we do not need to
|
|
97
|
-
const chorusGain = this.synth.chorusGain * this.synth.chorusSend * (chorusSend / CHORUS_DIVIDER);
|
|
98
|
-
const chorusLeftGain = gainLeft * chorusGain;
|
|
99
|
-
const chorusRightGain = gainRight * chorusGain;
|
|
100
|
-
for (let i = 0; i < inputBuffer.length; i++)
|
|
101
|
-
{
|
|
102
|
-
const idx = i + startIndex;
|
|
103
|
-
chorusLeft[idx] += chorusLeftGain * inputBuffer[i];
|
|
104
|
-
chorusRight[idx] += chorusRightGain * inputBuffer[i];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// mix down the audio data
|
|
110
|
-
if (gainLeft > 0)
|
|
111
|
-
{
|
|
112
|
-
for (let i = 0; i < inputBuffer.length; i++)
|
|
113
|
-
{
|
|
114
|
-
outputLeft[i + startIndex] += gainLeft * inputBuffer[i];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (gainRight > 0)
|
|
118
|
-
{
|
|
119
|
-
for (let i = 0; i < inputBuffer.length; i++)
|
|
120
|
-
{
|
|
121
|
-
outputRight[i + startIndex] += gainRight * inputBuffer[i];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|