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,402 +0,0 @@
|
|
|
1
|
-
import { decibelAttenuationToGain, timecentsToSeconds } from "./unit_converter.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_types.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* volume_envelope.js
|
|
8
|
-
* purpose: applies a volume envelope for a given voice
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export const VOLUME_ENVELOPE_SMOOTHING_FACTOR = 0.01;
|
|
12
|
-
|
|
13
|
-
const DB_SILENCE = 100;
|
|
14
|
-
const PERCEIVED_DB_SILENCE = 90;
|
|
15
|
-
// around 96 dB of attenuation
|
|
16
|
-
const PERCEIVED_GAIN_SILENCE = 0.000015; // can't go lower than that (see #50)
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* VOL ENV STATES:
|
|
20
|
-
* 0 - delay
|
|
21
|
-
* 1 - attack
|
|
22
|
-
* 2 - hold/peak
|
|
23
|
-
* 3 - decay
|
|
24
|
-
* 4 - sustain
|
|
25
|
-
* release indicates by isInRelease property
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export class VolumeEnvelope
|
|
29
|
-
{
|
|
30
|
-
/**
|
|
31
|
-
* The envelope's current time in samples
|
|
32
|
-
* @type {number}
|
|
33
|
-
*/
|
|
34
|
-
currentSampleTime = 0;
|
|
35
|
-
/**
|
|
36
|
-
* The sample rate in Hz
|
|
37
|
-
* @type {number}
|
|
38
|
-
*/
|
|
39
|
-
sampleRate;
|
|
40
|
-
/**
|
|
41
|
-
* The current attenuation of the envelope in dB
|
|
42
|
-
* @type {number}
|
|
43
|
-
*/
|
|
44
|
-
currentAttenuationDb = DB_SILENCE;
|
|
45
|
-
/**
|
|
46
|
-
* The current stage of the volume envelope
|
|
47
|
-
* @type {0|1|2|3|4}
|
|
48
|
-
*/
|
|
49
|
-
state = 0;
|
|
50
|
-
/**
|
|
51
|
-
* The dB attenuation of the envelope when it entered the release stage
|
|
52
|
-
* @type {number}
|
|
53
|
-
*/
|
|
54
|
-
releaseStartDb = DB_SILENCE;
|
|
55
|
-
/**
|
|
56
|
-
* The time in samples relative to the start of the envelope
|
|
57
|
-
* @type {number}
|
|
58
|
-
*/
|
|
59
|
-
releaseStartTimeSamples = 0;
|
|
60
|
-
/**
|
|
61
|
-
* The current gain applied to the voice in the release stage
|
|
62
|
-
* @type {number}
|
|
63
|
-
*/
|
|
64
|
-
currentReleaseGain = 1;
|
|
65
|
-
/**
|
|
66
|
-
* The attack duration in samples
|
|
67
|
-
* @type {number}
|
|
68
|
-
*/
|
|
69
|
-
attackDuration = 0;
|
|
70
|
-
/**
|
|
71
|
-
* The decay duration in samples
|
|
72
|
-
* @type {number}
|
|
73
|
-
*/
|
|
74
|
-
decayDuration = 0;
|
|
75
|
-
/**
|
|
76
|
-
* The release duration in samples
|
|
77
|
-
* @type {number}
|
|
78
|
-
*/
|
|
79
|
-
releaseDuration = 0;
|
|
80
|
-
/**
|
|
81
|
-
* The voice's absolute attenuation as linear gain
|
|
82
|
-
* @type {number}
|
|
83
|
-
*/
|
|
84
|
-
attenuation = 0;
|
|
85
|
-
/**
|
|
86
|
-
* The attenuation target, which the "attenuation" property is linearly interpolated towards (gain)
|
|
87
|
-
* @type {number}
|
|
88
|
-
*/
|
|
89
|
-
attenuationTargetGain = 0;
|
|
90
|
-
/**
|
|
91
|
-
* The attenuation target, which the "attenuation" property is linearly interpolated towards (dB)
|
|
92
|
-
* @type {number}
|
|
93
|
-
*/
|
|
94
|
-
attenuationTarget = 0;
|
|
95
|
-
/**
|
|
96
|
-
* The voice's sustain amount in dB, relative to attenuation
|
|
97
|
-
* @type {number}
|
|
98
|
-
*/
|
|
99
|
-
sustainDbRelative = 0;
|
|
100
|
-
/**
|
|
101
|
-
* The time in samples to the end of delay stage, relative to the start of the envelope
|
|
102
|
-
* @type {number}
|
|
103
|
-
*/
|
|
104
|
-
delayEnd = 0;
|
|
105
|
-
/**
|
|
106
|
-
* The time in samples to the end of attack stage, relative to the start of the envelope
|
|
107
|
-
* @type {number}
|
|
108
|
-
*/
|
|
109
|
-
attackEnd = 0;
|
|
110
|
-
/**
|
|
111
|
-
* The time in samples to the end of hold stage, relative to the start of the envelope
|
|
112
|
-
* @type {number}
|
|
113
|
-
*/
|
|
114
|
-
holdEnd = 0;
|
|
115
|
-
/**
|
|
116
|
-
* The time in samples to the end of decay stage, relative to the start of the envelope
|
|
117
|
-
* @type {number}
|
|
118
|
-
*/
|
|
119
|
-
decayEnd = 0;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @param sampleRate {number} Hz
|
|
123
|
-
* @param initialDecay {number} cb
|
|
124
|
-
*/
|
|
125
|
-
constructor(sampleRate, initialDecay)
|
|
126
|
-
{
|
|
127
|
-
this.sampleRate = sampleRate;
|
|
128
|
-
/**
|
|
129
|
-
* if sustain stge is silent,
|
|
130
|
-
* then we can turn off the voice when it is silent.
|
|
131
|
-
* We can't do that with modulated as it can silence the volume and then raise it again, and the voice must keep playing
|
|
132
|
-
* @type {boolean}
|
|
133
|
-
*/
|
|
134
|
-
this.canEndOnSilentSustain = initialDecay / 10 >= PERCEIVED_DB_SILENCE;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Starts the release phase in the envelope
|
|
139
|
-
* @param voice {Voice} the voice this envelope belongs to
|
|
140
|
-
*/
|
|
141
|
-
static startRelease(voice)
|
|
142
|
-
{
|
|
143
|
-
voice.volumeEnvelope.releaseStartTimeSamples = voice.volumeEnvelope.currentSampleTime;
|
|
144
|
-
voice.volumeEnvelope.currentReleaseGain = decibelAttenuationToGain(voice.volumeEnvelope.currentAttenuationDb);
|
|
145
|
-
VolumeEnvelope.recalculate(voice);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Recalculates the envelope
|
|
150
|
-
* @param voice {Voice} the voice this envelope belongs to
|
|
151
|
-
*/
|
|
152
|
-
static recalculate(voice)
|
|
153
|
-
{
|
|
154
|
-
const env = voice.volumeEnvelope;
|
|
155
|
-
const timecentsToSamples = tc =>
|
|
156
|
-
{
|
|
157
|
-
return Math.max(0, Math.floor(timecentsToSeconds(tc) * env.sampleRate));
|
|
158
|
-
};
|
|
159
|
-
// calculate absolute times (they can change so we have to recalculate every time
|
|
160
|
-
env.attenuationTarget = Math.max(
|
|
161
|
-
0,
|
|
162
|
-
Math.min(voice.modulatedGenerators[generatorTypes.initialAttenuation], 1440)
|
|
163
|
-
) / 10; // divide by ten to get decibels
|
|
164
|
-
env.attenuationTargetGain = decibelAttenuationToGain(env.attenuationTarget);
|
|
165
|
-
env.sustainDbRelative = Math.min(DB_SILENCE, voice.modulatedGenerators[generatorTypes.sustainVolEnv] / 10);
|
|
166
|
-
const sustainDb = Math.min(DB_SILENCE, env.sustainDbRelative);
|
|
167
|
-
|
|
168
|
-
// calculate durations
|
|
169
|
-
env.attackDuration = timecentsToSamples(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
|
|
170
|
-
|
|
171
|
-
// decay: sfspec page 35: the time is for change from attenuation to -100dB,
|
|
172
|
-
// therefore, we need to calculate the real time
|
|
173
|
-
// (changing from attenuation to sustain instead of -100dB)
|
|
174
|
-
const fullChange = voice.modulatedGenerators[generatorTypes.decayVolEnv];
|
|
175
|
-
const keyNumAddition = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay];
|
|
176
|
-
const fraction = sustainDb / DB_SILENCE;
|
|
177
|
-
env.decayDuration = timecentsToSamples(fullChange + keyNumAddition) * fraction;
|
|
178
|
-
|
|
179
|
-
env.releaseDuration = timecentsToSamples(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
|
|
180
|
-
|
|
181
|
-
// calculate absolute end times for the values
|
|
182
|
-
env.delayEnd = timecentsToSamples(voice.modulatedGenerators[generatorTypes.delayVolEnv]);
|
|
183
|
-
env.attackEnd = env.attackDuration + env.delayEnd;
|
|
184
|
-
|
|
185
|
-
// make sure to take keyNumToVolEnvHold into account!
|
|
186
|
-
const holdExcursion = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvHold];
|
|
187
|
-
env.holdEnd = timecentsToSamples(voice.modulatedGenerators[generatorTypes.holdVolEnv]
|
|
188
|
-
+ holdExcursion)
|
|
189
|
-
+ env.attackEnd;
|
|
190
|
-
|
|
191
|
-
env.decayEnd = env.decayDuration + env.holdEnd;
|
|
192
|
-
|
|
193
|
-
// if this is the first recalculation and the voice has no attack or delay time, set current db to peak
|
|
194
|
-
if (env.state === 0 && env.attackEnd === 0)
|
|
195
|
-
{
|
|
196
|
-
// env.currentAttenuationDb = env.attenuationTarget;
|
|
197
|
-
env.state = 2;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// check if voice is in release
|
|
201
|
-
if (voice.isInRelease)
|
|
202
|
-
{
|
|
203
|
-
// no interpolation this time: force update to actual attenuation and calculate release start from there
|
|
204
|
-
//env.attenuation = Math.min(DB_SILENCE, env.attenuationTarget);
|
|
205
|
-
const sustainDb = Math.max(0, Math.min(DB_SILENCE, env.sustainDbRelative));
|
|
206
|
-
const fraction = sustainDb / DB_SILENCE;
|
|
207
|
-
env.decayDuration = timecentsToSamples(fullChange + keyNumAddition) * fraction;
|
|
208
|
-
|
|
209
|
-
switch (env.state)
|
|
210
|
-
{
|
|
211
|
-
case 0:
|
|
212
|
-
env.releaseStartDb = DB_SILENCE;
|
|
213
|
-
break;
|
|
214
|
-
|
|
215
|
-
case 1:
|
|
216
|
-
// attack phase: get linear gain of the attack phase when release started
|
|
217
|
-
// and turn it into db as we're ramping the db up linearly
|
|
218
|
-
// (to make volume go down exponentially)
|
|
219
|
-
// attack is linear (in gain) so we need to do get db from that
|
|
220
|
-
let elapsed = 1 - ((env.attackEnd - env.releaseStartTimeSamples) / env.attackDuration);
|
|
221
|
-
// calculate the gain that the attack would have, so
|
|
222
|
-
// turn that into db
|
|
223
|
-
env.releaseStartDb = 20 * Math.log10(elapsed) * -1;
|
|
224
|
-
break;
|
|
225
|
-
|
|
226
|
-
case 2:
|
|
227
|
-
env.releaseStartDb = 0;
|
|
228
|
-
break;
|
|
229
|
-
|
|
230
|
-
case 3:
|
|
231
|
-
env.releaseStartDb = (1 - (env.decayEnd - env.releaseStartTimeSamples) / env.decayDuration) * sustainDb;
|
|
232
|
-
break;
|
|
233
|
-
|
|
234
|
-
case 4:
|
|
235
|
-
env.releaseStartDb = sustainDb;
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
env.releaseStartDb = Math.max(0, Math.min(env.releaseStartDb, DB_SILENCE));
|
|
239
|
-
if (env.releaseStartDb >= PERCEIVED_DB_SILENCE)
|
|
240
|
-
{
|
|
241
|
-
voice.finished = true;
|
|
242
|
-
}
|
|
243
|
-
env.currentReleaseGain = decibelAttenuationToGain(env.releaseStartDb);
|
|
244
|
-
|
|
245
|
-
// release: sfspec page 35: the time is for change from attenuation to -100dB,
|
|
246
|
-
// therefore, we need to calculate the real time
|
|
247
|
-
// (changing from release start to -100dB instead of from peak to -100dB)
|
|
248
|
-
const releaseFraction = (DB_SILENCE - env.releaseStartDb) / DB_SILENCE;
|
|
249
|
-
env.releaseDuration *= releaseFraction;
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Applies volume envelope gain to the given output buffer
|
|
256
|
-
* @param voice {Voice} the voice we're working on
|
|
257
|
-
* @param audioBuffer {Float32Array} the audio buffer to modify
|
|
258
|
-
* @param centibelOffset {number} the centibel offset of volume, for modLFOtoVolume
|
|
259
|
-
* @param smoothingFactor {number} the adjusted smoothing factor for the envelope
|
|
260
|
-
* @description essentially we use approach of 100dB is silence, 0dB is peak, and always add attenuation to that (which is interpolated)
|
|
261
|
-
*/
|
|
262
|
-
static apply(voice, audioBuffer, centibelOffset, smoothingFactor)
|
|
263
|
-
{
|
|
264
|
-
const env = voice.volumeEnvelope;
|
|
265
|
-
let decibelOffset = centibelOffset / 10;
|
|
266
|
-
|
|
267
|
-
const attenuationSmoothing = smoothingFactor;
|
|
268
|
-
|
|
269
|
-
// RELEASE PHASE
|
|
270
|
-
if (voice.isInRelease)
|
|
271
|
-
{
|
|
272
|
-
let elapsedRelease = env.currentSampleTime - env.releaseStartTimeSamples;
|
|
273
|
-
if (elapsedRelease >= env.releaseDuration)
|
|
274
|
-
{
|
|
275
|
-
for (let i = 0; i < audioBuffer.length; i++)
|
|
276
|
-
{
|
|
277
|
-
audioBuffer[i] = 0;
|
|
278
|
-
}
|
|
279
|
-
voice.finished = true;
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
let dbDifference = DB_SILENCE - env.releaseStartDb;
|
|
283
|
-
for (let i = 0; i < audioBuffer.length; i++)
|
|
284
|
-
{
|
|
285
|
-
// attenuation interpolation
|
|
286
|
-
env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
|
|
287
|
-
let db = (elapsedRelease / env.releaseDuration) * dbDifference + env.releaseStartDb;
|
|
288
|
-
env.currentReleaseGain = env.attenuation * decibelAttenuationToGain(db + decibelOffset);
|
|
289
|
-
audioBuffer[i] *= env.currentReleaseGain;
|
|
290
|
-
env.currentSampleTime++;
|
|
291
|
-
elapsedRelease++;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (env.currentReleaseGain <= PERCEIVED_GAIN_SILENCE)
|
|
295
|
-
{
|
|
296
|
-
voice.finished = true;
|
|
297
|
-
}
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
let filledBuffer = 0;
|
|
302
|
-
switch (env.state)
|
|
303
|
-
{
|
|
304
|
-
case 0:
|
|
305
|
-
// delay phase, no sound is produced
|
|
306
|
-
while (env.currentSampleTime < env.delayEnd)
|
|
307
|
-
{
|
|
308
|
-
env.currentAttenuationDb = DB_SILENCE;
|
|
309
|
-
audioBuffer[filledBuffer] = 0;
|
|
310
|
-
|
|
311
|
-
env.currentSampleTime++;
|
|
312
|
-
if (++filledBuffer >= audioBuffer.length)
|
|
313
|
-
{
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
env.state++;
|
|
318
|
-
// fallthrough
|
|
319
|
-
|
|
320
|
-
case 1:
|
|
321
|
-
// attack phase: ramp from 0 to attenuation
|
|
322
|
-
while (env.currentSampleTime < env.attackEnd)
|
|
323
|
-
{
|
|
324
|
-
// attenuation interpolation
|
|
325
|
-
env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
|
|
326
|
-
|
|
327
|
-
// Special case: linear gain ramp instead of linear db ramp
|
|
328
|
-
let linearAttenuation = 1 - (env.attackEnd - env.currentSampleTime) / env.attackDuration; // 0 to 1
|
|
329
|
-
audioBuffer[filledBuffer] *= linearAttenuation * env.attenuation * decibelAttenuationToGain(
|
|
330
|
-
decibelOffset);
|
|
331
|
-
// set current attenuation to peak as its invalid during this phase
|
|
332
|
-
env.currentAttenuationDb = 0;
|
|
333
|
-
|
|
334
|
-
env.currentSampleTime++;
|
|
335
|
-
if (++filledBuffer >= audioBuffer.length)
|
|
336
|
-
{
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
env.state++;
|
|
341
|
-
// fallthrough
|
|
342
|
-
|
|
343
|
-
case 2:
|
|
344
|
-
// hold/peak phase: stay at attenuation
|
|
345
|
-
while (env.currentSampleTime < env.holdEnd)
|
|
346
|
-
{
|
|
347
|
-
// attenuation interpolation
|
|
348
|
-
env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
|
|
349
|
-
|
|
350
|
-
audioBuffer[filledBuffer] *= env.attenuation * decibelAttenuationToGain(decibelOffset);
|
|
351
|
-
env.currentAttenuationDb = 0;
|
|
352
|
-
|
|
353
|
-
env.currentSampleTime++;
|
|
354
|
-
if (++filledBuffer >= audioBuffer.length)
|
|
355
|
-
{
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
env.state++;
|
|
360
|
-
// fallthrough
|
|
361
|
-
|
|
362
|
-
case 3:
|
|
363
|
-
// decay phase: linear ramp from attenuation to sustain
|
|
364
|
-
while (env.currentSampleTime < env.decayEnd)
|
|
365
|
-
{
|
|
366
|
-
// attenuation interpolation
|
|
367
|
-
env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
|
|
368
|
-
|
|
369
|
-
env.currentAttenuationDb = (1 - (env.decayEnd - env.currentSampleTime) / env.decayDuration) * env.sustainDbRelative;
|
|
370
|
-
audioBuffer[filledBuffer] *= env.attenuation * decibelAttenuationToGain(env.currentAttenuationDb + decibelOffset);
|
|
371
|
-
|
|
372
|
-
env.currentSampleTime++;
|
|
373
|
-
if (++filledBuffer >= audioBuffer.length)
|
|
374
|
-
{
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
env.state++;
|
|
379
|
-
// fallthrough
|
|
380
|
-
|
|
381
|
-
case 4:
|
|
382
|
-
if (env.canEndOnSilentSustain && env.sustainDbRelative >= PERCEIVED_DB_SILENCE)
|
|
383
|
-
{
|
|
384
|
-
voice.finished = true;
|
|
385
|
-
}
|
|
386
|
-
// sustain phase: stay at sustain
|
|
387
|
-
while (true)
|
|
388
|
-
{
|
|
389
|
-
// attenuation interpolation
|
|
390
|
-
env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
|
|
391
|
-
|
|
392
|
-
audioBuffer[filledBuffer] *= env.attenuation * decibelAttenuationToGain(env.sustainDbRelative + decibelOffset);
|
|
393
|
-
env.currentAttenuationDb = env.sustainDbRelative;
|
|
394
|
-
env.currentSampleTime++;
|
|
395
|
-
if (++filledBuffer >= audioBuffer.length)
|
|
396
|
-
{
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { interpolationTypes } from "./enums.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* wavetable_oscillator.js
|
|
5
|
-
* purpose: plays back raw audio data at an arbitrary playback rate
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export class WavetableOscillator
|
|
10
|
-
{
|
|
11
|
-
/**
|
|
12
|
-
* Fills the output buffer with raw sample data using a given interpolation
|
|
13
|
-
* @param voice {Voice} the voice we're working on
|
|
14
|
-
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
15
|
-
* @param interpolation {interpolationTypes} the interpolation type
|
|
16
|
-
*/
|
|
17
|
-
static getSample(voice, outputBuffer, interpolation)
|
|
18
|
-
{
|
|
19
|
-
const step = voice.currentTuningCalculated * voice.sample.playbackStep;
|
|
20
|
-
// why not?
|
|
21
|
-
if (step === 1)
|
|
22
|
-
{
|
|
23
|
-
WavetableOscillator.getSampleNearest(voice, outputBuffer, step);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
switch (interpolation)
|
|
27
|
-
{
|
|
28
|
-
case interpolationTypes.fourthOrder:
|
|
29
|
-
this.getSampleHermite(voice, outputBuffer, step);
|
|
30
|
-
return;
|
|
31
|
-
|
|
32
|
-
case interpolationTypes.linear:
|
|
33
|
-
default:
|
|
34
|
-
this.getSampleLinear(voice, outputBuffer, step);
|
|
35
|
-
return;
|
|
36
|
-
|
|
37
|
-
case interpolationTypes.nearestNeighbor:
|
|
38
|
-
WavetableOscillator.getSampleNearest(voice, outputBuffer, step);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Fills the output buffer with raw sample data using linear interpolation
|
|
45
|
-
* @param voice {Voice} the voice we're working on
|
|
46
|
-
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
47
|
-
* @param step {number} the step to advance every sample
|
|
48
|
-
*/
|
|
49
|
-
static getSampleLinear(voice, outputBuffer, step)
|
|
50
|
-
{
|
|
51
|
-
const sample = voice.sample;
|
|
52
|
-
let cur = sample.cursor;
|
|
53
|
-
const sampleData = sample.sampleData;
|
|
54
|
-
|
|
55
|
-
if (sample.isLooping)
|
|
56
|
-
{
|
|
57
|
-
const loopLength = sample.loopEnd - sample.loopStart;
|
|
58
|
-
for (let i = 0; i < outputBuffer.length; i++)
|
|
59
|
-
{
|
|
60
|
-
// check for loop
|
|
61
|
-
while (cur >= sample.loopEnd)
|
|
62
|
-
{
|
|
63
|
-
cur -= loopLength;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// grab the 2 nearest points
|
|
67
|
-
const floor = ~~cur;
|
|
68
|
-
let ceil = floor + 1;
|
|
69
|
-
|
|
70
|
-
while (ceil >= sample.loopEnd)
|
|
71
|
-
{
|
|
72
|
-
ceil -= loopLength;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const fraction = cur - floor;
|
|
76
|
-
|
|
77
|
-
// grab the samples and interpolate
|
|
78
|
-
const upper = sampleData[ceil];
|
|
79
|
-
const lower = sampleData[floor];
|
|
80
|
-
outputBuffer[i] = (lower + (upper - lower) * fraction);
|
|
81
|
-
|
|
82
|
-
cur += step;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
else
|
|
86
|
-
{
|
|
87
|
-
for (let i = 0; i < outputBuffer.length; i++)
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
// linear interpolation
|
|
91
|
-
const floor = ~~cur;
|
|
92
|
-
const ceil = floor + 1;
|
|
93
|
-
|
|
94
|
-
// flag the voice as finished if needed
|
|
95
|
-
if (ceil >= sample.end)
|
|
96
|
-
{
|
|
97
|
-
voice.finished = true;
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const fraction = cur - floor;
|
|
102
|
-
|
|
103
|
-
// grab the samples and interpolate
|
|
104
|
-
const upper = sampleData[ceil];
|
|
105
|
-
const lower = sampleData[floor];
|
|
106
|
-
outputBuffer[i] = (lower + (upper - lower) * fraction);
|
|
107
|
-
|
|
108
|
-
cur += step;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
voice.sample.cursor = cur;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Fills the output buffer with raw sample data using no interpolation (nearest neighbor)
|
|
116
|
-
* @param voice {Voice} the voice we're working on
|
|
117
|
-
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
118
|
-
* @param step {number} the step to advance every sample
|
|
119
|
-
*/
|
|
120
|
-
static getSampleNearest(voice, outputBuffer, step)
|
|
121
|
-
{
|
|
122
|
-
const sample = voice.sample;
|
|
123
|
-
let cur = sample.cursor;
|
|
124
|
-
const sampleData = sample.sampleData;
|
|
125
|
-
|
|
126
|
-
if (sample.isLooping)
|
|
127
|
-
{
|
|
128
|
-
const loopLength = sample.loopEnd - sample.loopStart;
|
|
129
|
-
for (let i = 0; i < outputBuffer.length; i++)
|
|
130
|
-
{
|
|
131
|
-
// check for loop
|
|
132
|
-
while (cur >= sample.loopEnd)
|
|
133
|
-
{
|
|
134
|
-
cur -= loopLength;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// grab the nearest neighbor
|
|
138
|
-
let ceil = ~~cur + 1;
|
|
139
|
-
|
|
140
|
-
while (ceil >= sample.loopEnd)
|
|
141
|
-
{
|
|
142
|
-
ceil -= loopLength;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
outputBuffer[i] = sampleData[ceil];
|
|
146
|
-
cur += step;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else
|
|
150
|
-
{
|
|
151
|
-
for (let i = 0; i < outputBuffer.length; i++)
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
// nearest neighbor
|
|
155
|
-
const ceil = ~~cur + 1;
|
|
156
|
-
|
|
157
|
-
// flag the voice as finished if needed
|
|
158
|
-
if (ceil >= sample.end)
|
|
159
|
-
{
|
|
160
|
-
voice.finished = true;
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
outputBuffer[i] = sampleData[ceil];
|
|
165
|
-
cur += step;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
sample.cursor = cur;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Fills the output buffer with raw sample data using Hermite interpolation
|
|
174
|
-
* @param voice {Voice} the voice we're working on
|
|
175
|
-
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
176
|
-
* @param step {number} the step to advance every sample
|
|
177
|
-
*/
|
|
178
|
-
static getSampleHermite(voice, outputBuffer, step)
|
|
179
|
-
{
|
|
180
|
-
const sample = voice.sample;
|
|
181
|
-
let cur = sample.cursor;
|
|
182
|
-
const sampleData = sample.sampleData;
|
|
183
|
-
|
|
184
|
-
if (sample.isLooping)
|
|
185
|
-
{
|
|
186
|
-
const loopLength = sample.loopEnd - sample.loopStart;
|
|
187
|
-
for (let i = 0; i < outputBuffer.length; i++)
|
|
188
|
-
{
|
|
189
|
-
// check for loop (it can exceed the end point multiple times)
|
|
190
|
-
while (cur >= sample.loopEnd)
|
|
191
|
-
{
|
|
192
|
-
cur -= loopLength;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// grab the 4 points
|
|
196
|
-
const y0 = ~~cur; // point before the cursor. twice bitwise-not is just a faster Math.floor
|
|
197
|
-
let y1 = y0 + 1; // point after the cursor
|
|
198
|
-
let y2 = y0 + 2; // point 1 after the cursor
|
|
199
|
-
let y3 = y0 + 3; // point 2 after the cursor
|
|
200
|
-
const t = cur - y0; // the distance from y0 to cursor [0;1]
|
|
201
|
-
// y0 is not handled here
|
|
202
|
-
// as it's math.floor of cur which is handled above
|
|
203
|
-
if (y1 >= sample.loopEnd)
|
|
204
|
-
{
|
|
205
|
-
y1 -= loopLength;
|
|
206
|
-
}
|
|
207
|
-
if (y2 >= sample.loopEnd)
|
|
208
|
-
{
|
|
209
|
-
y2 -= loopLength;
|
|
210
|
-
}
|
|
211
|
-
if (y3 >= sample.loopEnd)
|
|
212
|
-
{
|
|
213
|
-
y3 -= loopLength;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// grab the samples
|
|
217
|
-
const xm1 = sampleData[y0];
|
|
218
|
-
const x0 = sampleData[y1];
|
|
219
|
-
const x1 = sampleData[y2];
|
|
220
|
-
const x2 = sampleData[y3];
|
|
221
|
-
|
|
222
|
-
// interpolate
|
|
223
|
-
// https://www.musicdsp.org/en/latest/Other/93-hermite-interpollation.html
|
|
224
|
-
const c = (x1 - xm1) * 0.5;
|
|
225
|
-
const v = x0 - x1;
|
|
226
|
-
const w = c + v;
|
|
227
|
-
const a = w + v + (x2 - x0) * 0.5;
|
|
228
|
-
const b = w + a;
|
|
229
|
-
outputBuffer[i] = ((((a * t) - b) * t + c) * t + x0);
|
|
230
|
-
|
|
231
|
-
cur += step;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else
|
|
235
|
-
{
|
|
236
|
-
for (let i = 0; i < outputBuffer.length; i++)
|
|
237
|
-
{
|
|
238
|
-
// grab the 4 points
|
|
239
|
-
const y0 = ~~cur; // point before the cursor. twice bitwise-not is just a faster Math.floor
|
|
240
|
-
let y1 = y0 + 1; // point after the cursor
|
|
241
|
-
let y2 = y0 + 2; // point 1 after the cursor
|
|
242
|
-
let y3 = y0 + 3; // point 2 after the cursor
|
|
243
|
-
const t = cur - y0; // the distance from y0 to cursor [0;1]
|
|
244
|
-
|
|
245
|
-
// flag as finished if needed
|
|
246
|
-
if (y1 >= sample.end ||
|
|
247
|
-
y2 >= sample.end ||
|
|
248
|
-
y3 >= sample.end)
|
|
249
|
-
{
|
|
250
|
-
voice.finished = true;
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// grab the samples
|
|
255
|
-
const xm1 = sampleData[y0];
|
|
256
|
-
const x0 = sampleData[y1];
|
|
257
|
-
const x1 = sampleData[y2];
|
|
258
|
-
const x2 = sampleData[y3];
|
|
259
|
-
|
|
260
|
-
// interpolate
|
|
261
|
-
// https://www.musicdsp.org/en/latest/Other/93-hermite-interpollation.html
|
|
262
|
-
const c = (x1 - xm1) * 0.5;
|
|
263
|
-
const v = x0 - x1;
|
|
264
|
-
const w = c + v;
|
|
265
|
-
const a = w + v + (x2 - x0) * 0.5;
|
|
266
|
-
const b = w + a;
|
|
267
|
-
outputBuffer[i] = ((((a * t) - b) * t + c) * t + x0);
|
|
268
|
-
|
|
269
|
-
cur += step;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
voice.sample.cursor = cur;
|
|
273
|
-
}
|
|
274
|
-
}
|