spessasynth_core 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +3 -26
- package/README.md +156 -474
- package/index.js +73 -8
- package/package.json +21 -8
- package/src/externals/fflate/LICENSE +21 -0
- package/src/externals/fflate/fflate.min.js +1 -0
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/src/externals/stbvorbis_sync/LICENSE +202 -0
- package/src/externals/stbvorbis_sync/NOTICE +6 -0
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/src/midi/README.md +32 -0
- package/src/midi/basic_midi.js +567 -0
- package/src/midi/midi_builder.js +202 -0
- package/src/midi/midi_loader.js +324 -0
- package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
- package/src/midi/midi_sequence.js +224 -0
- package/src/midi/midi_tools/get_note_times.js +154 -0
- package/src/midi/midi_tools/midi_editor.js +611 -0
- package/src/midi/midi_tools/midi_writer.js +99 -0
- package/src/midi/midi_tools/rmidi_writer.js +567 -0
- package/src/midi/midi_tools/used_keys_loaded.js +238 -0
- package/src/midi/xmf_loader.js +454 -0
- package/src/sequencer/README.md +5 -0
- package/src/sequencer/events.js +81 -0
- package/src/sequencer/play.js +349 -0
- package/src/sequencer/process_event.js +165 -0
- package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
- package/src/sequencer/sequencer_engine.js +367 -0
- package/src/sequencer/song_control.js +201 -0
- package/src/soundfont/README.md +13 -0
- package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
- package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
- package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
- package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
- package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
- package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
- package/src/soundfont/basic_soundfont/generator.js +220 -0
- package/src/soundfont/basic_soundfont/modulator.js +378 -0
- package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
- package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
- package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
- package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
- package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
- package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
- package/src/soundfont/dls/articulator_converter.js +396 -0
- package/src/soundfont/dls/dls_destinations.js +38 -0
- package/src/soundfont/dls/dls_preset.js +44 -0
- package/src/soundfont/dls/dls_sample.js +75 -0
- package/src/soundfont/dls/dls_soundfont.js +186 -0
- package/src/soundfont/dls/dls_sources.js +62 -0
- package/src/soundfont/dls/dls_zone.js +95 -0
- package/src/soundfont/dls/read_articulation.js +299 -0
- package/src/soundfont/dls/read_instrument.js +121 -0
- package/src/soundfont/dls/read_instrument_list.js +17 -0
- package/src/soundfont/dls/read_lart.js +35 -0
- package/src/soundfont/dls/read_region.js +152 -0
- package/src/soundfont/dls/read_samples.js +270 -0
- package/src/soundfont/load_soundfont.js +21 -0
- package/src/soundfont/read_sf2/generators.js +46 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
- package/src/soundfont/read_sf2/modulators.js +36 -0
- package/src/soundfont/read_sf2/presets.js +80 -0
- package/src/soundfont/read_sf2/samples.js +304 -0
- package/src/soundfont/read_sf2/soundfont.js +305 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
- package/src/synthetizer/README.md +7 -0
- package/src/synthetizer/audio_engine/README.md +9 -0
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
- package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
- package/src/synthetizer/audio_engine/main_processor.js +804 -0
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
- package/src/synthetizer/synth_constants.js +22 -0
- package/{spessasynth_core → src}/utils/README.md +1 -0
- package/src/utils/buffer_to_wav.js +185 -0
- package/src/utils/byte_functions/big_endian.js +32 -0
- package/src/utils/byte_functions/little_endian.js +77 -0
- package/src/utils/byte_functions/string.js +107 -0
- package/src/utils/byte_functions/variable_length_quantity.js +42 -0
- package/src/utils/fill_with_defaults.js +21 -0
- package/src/utils/indexed_array.js +52 -0
- package/{spessasynth_core → src}/utils/loggin.js +70 -78
- package/src/utils/other.js +92 -0
- package/src/utils/sysex_detector.js +58 -0
- package/src/utils/xg_hacks.js +193 -0
- package/.idea/inspectionProfiles/Project_Default.xml +0 -10
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/spessasynth_core.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/spessasynth_core/midi_parser/README.md +0 -3
- package/spessasynth_core/midi_parser/midi_loader.js +0 -386
- package/spessasynth_core/sequencer/sequencer.js +0 -202
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
- package/spessasynth_core/soundfont/README.md +0 -4
- package/spessasynth_core/soundfont/chunk/generators.js +0 -205
- package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
- package/spessasynth_core/soundfont/chunk/presets.js +0 -264
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
- package/spessasynth_core/soundfont/chunk/samples.js +0 -250
- package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
- package/spessasynth_core/synthetizer/README.md +0 -6
- package/spessasynth_core/synthetizer/synthesizer.js +0 -303
- 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 -222
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -95
- 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 -194
- 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 -173
- 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 -313
- 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,222 +0,0 @@
|
|
|
1
|
-
import { generatorTypes } from '../../../soundfont/chunk/generators.js'
|
|
2
|
-
import { absCentsToHz, timecentsToSeconds } from '../worklet_utilities/unit_converter.js'
|
|
3
|
-
import { getLFOValue } from '../worklet_utilities/lfo.js'
|
|
4
|
-
import { customControllers } from '../worklet_utilities/worklet_processor_channel.js'
|
|
5
|
-
import { getModEnvValue } from '../worklet_utilities/modulation_envelope.js'
|
|
6
|
-
import { getOscillatorData } from '../worklet_utilities/wavetable_oscillator.js'
|
|
7
|
-
import { panVoice } from '../worklet_utilities/stereo_panner.js'
|
|
8
|
-
import { applyVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
|
|
9
|
-
import { applyLowpassFilter } from '../worklet_utilities/lowpass_filter.js'
|
|
10
|
-
import { MIN_NOTE_LENGTH } from '../../synthesizer.js'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Renders a voice to the stereo output buffer
|
|
14
|
-
* @param channel {WorkletProcessorChannel} the voice's channel
|
|
15
|
-
* @param voice {WorkletVoice} the voice to render
|
|
16
|
-
* @param output {Float32Array[]} the output buffer
|
|
17
|
-
* @param reverbOutput {Float32Array[]} output for reverb
|
|
18
|
-
* @param chorusOutput {Float32Array[]} output for chorus
|
|
19
|
-
* @this {Synthesizer}
|
|
20
|
-
*/
|
|
21
|
-
export function renderVoice(channel, voice, output, reverbOutput, chorusOutput)
|
|
22
|
-
{
|
|
23
|
-
// if no matching sample, perhaps it's still being loaded...?
|
|
24
|
-
if(this.workletDumpedSamplesList[voice.sample.sampleID] === undefined)
|
|
25
|
-
{
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// check if release
|
|
30
|
-
if(!voice.isInRelease) {
|
|
31
|
-
// if not in release, check if the release time is
|
|
32
|
-
if (this.currentTime >= voice.releaseStartTime) {
|
|
33
|
-
voice.releaseStartModEnv = voice.currentModEnvValue;
|
|
34
|
-
voice.isInRelease = true;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// if the initial attenuation is more than 100dB, skip the voice (it's silent anyway)
|
|
40
|
-
if(voice.modulatedGenerators[generatorTypes.initialAttenuation] > 2500)
|
|
41
|
-
{
|
|
42
|
-
if(voice.isInRelease)
|
|
43
|
-
{
|
|
44
|
-
voice.finished = true;
|
|
45
|
-
}
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// TUNING
|
|
50
|
-
|
|
51
|
-
// calculate tuning
|
|
52
|
-
let cents = voice.modulatedGenerators[generatorTypes.fineTune]
|
|
53
|
-
+ channel.customControllers[customControllers.channelTuning]
|
|
54
|
-
+ channel.customControllers[customControllers.channelTranspose]
|
|
55
|
-
+ channel.customControllers[customControllers.masterTuning];
|
|
56
|
-
let semitones = voice.modulatedGenerators[generatorTypes.coarseTune];
|
|
57
|
-
|
|
58
|
-
// calculate tuning by key
|
|
59
|
-
cents += (voice.targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
|
|
60
|
-
|
|
61
|
-
// vibrato LFO
|
|
62
|
-
const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
|
|
63
|
-
if(vibratoDepth > 0)
|
|
64
|
-
{
|
|
65
|
-
const vibStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVibLFO]);
|
|
66
|
-
const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
|
|
67
|
-
const lfoVal = getLFOValue(vibStart, vibFreqHz, this.currentTime);
|
|
68
|
-
if(lfoVal)
|
|
69
|
-
{
|
|
70
|
-
cents += lfoVal * (vibratoDepth * channel.customControllers[customControllers.modulationMultiplier]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// lowpass frequency
|
|
75
|
-
let lowpassCents = voice.modulatedGenerators[generatorTypes.initialFilterFc];
|
|
76
|
-
|
|
77
|
-
// mod LFO
|
|
78
|
-
const modPitchDepth = voice.modulatedGenerators[generatorTypes.modLfoToPitch];
|
|
79
|
-
const modVolDepth = voice.modulatedGenerators[generatorTypes.modLfoToVolume];
|
|
80
|
-
const modFilterDepth = voice.modulatedGenerators[generatorTypes.modLfoToFilterFc];
|
|
81
|
-
let modLfoCentibels = 0;
|
|
82
|
-
if(modPitchDepth + modFilterDepth + modVolDepth > 0)
|
|
83
|
-
{
|
|
84
|
-
const modStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModLFO]);
|
|
85
|
-
const modFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqModLFO]);
|
|
86
|
-
const modLfoValue = getLFOValue(modStart, modFreqHz, this.currentTime);
|
|
87
|
-
cents += modLfoValue * (modPitchDepth * channel.customControllers[customControllers.modulationMultiplier]);
|
|
88
|
-
modLfoCentibels = modLfoValue * modVolDepth;
|
|
89
|
-
lowpassCents += modLfoValue * modFilterDepth;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// channel vibrato (GS NRPN)
|
|
93
|
-
if(channel.channelVibrato.depth > 0)
|
|
94
|
-
{
|
|
95
|
-
const channelVibrato = getLFOValue(voice.startTime + channel.channelVibrato.delay, channel.channelVibrato.rate, this.currentTime);
|
|
96
|
-
if(channelVibrato)
|
|
97
|
-
{
|
|
98
|
-
cents += channelVibrato * channel.channelVibrato.depth;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// mod env
|
|
103
|
-
const modEnvPitchDepth = voice.modulatedGenerators[generatorTypes.modEnvToPitch];
|
|
104
|
-
const modEnvFilterDepth = voice.modulatedGenerators[generatorTypes.modEnvToFilterFc];
|
|
105
|
-
const modEnv = getModEnvValue(voice, this.currentTime);
|
|
106
|
-
lowpassCents += modEnv * modEnvFilterDepth;
|
|
107
|
-
cents += modEnv * modEnvPitchDepth;
|
|
108
|
-
|
|
109
|
-
// finally calculate the playback rate
|
|
110
|
-
const centsTotal = ~~(cents + semitones * 100);
|
|
111
|
-
if(centsTotal !== voice.currentTuningCents)
|
|
112
|
-
{
|
|
113
|
-
voice.currentTuningCents = centsTotal;
|
|
114
|
-
voice.currentTuningCalculated = Math.pow(2, centsTotal / 1200);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// PANNING
|
|
118
|
-
const pan = ( (Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan] )) + 500) / 1000) ; // 0 to 1
|
|
119
|
-
|
|
120
|
-
// SYNTHESIS
|
|
121
|
-
const bufferOut = new Float32Array(output[0].length);
|
|
122
|
-
|
|
123
|
-
// wavetable oscillator
|
|
124
|
-
getOscillatorData(voice, this.workletDumpedSamplesList[voice.sample.sampleID], bufferOut);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// lowpass filter
|
|
128
|
-
applyLowpassFilter(voice, bufferOut, lowpassCents, this.sampleRate);
|
|
129
|
-
|
|
130
|
-
// volenv
|
|
131
|
-
applyVolumeEnvelope(voice, bufferOut, this.currentTime, modLfoCentibels, this.sampleTime);
|
|
132
|
-
|
|
133
|
-
// pan the voice and write out
|
|
134
|
-
voice.currentPan += (pan - voice.currentPan) * 0.1; // smooth out pan to prevent clicking
|
|
135
|
-
const panLeft = (1 - voice.currentPan) * this.panLeft;
|
|
136
|
-
const panRight = voice.currentPan * this.panRight;
|
|
137
|
-
panVoice(
|
|
138
|
-
panLeft,
|
|
139
|
-
panRight,
|
|
140
|
-
bufferOut,
|
|
141
|
-
output,
|
|
142
|
-
reverbOutput, voice.modulatedGenerators[generatorTypes.reverbEffectsSend],
|
|
143
|
-
chorusOutput, voice.modulatedGenerators[generatorTypes.chorusEffectsSend]);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @param channel {WorkletProcessorChannel}
|
|
148
|
-
* @param voice {WorkletVoice}
|
|
149
|
-
* @return {number}
|
|
150
|
-
*/
|
|
151
|
-
function getPriority(channel, voice)
|
|
152
|
-
{
|
|
153
|
-
let priority = 0;
|
|
154
|
-
if(channel.drumChannel)
|
|
155
|
-
{
|
|
156
|
-
// important
|
|
157
|
-
priority += 5;
|
|
158
|
-
}
|
|
159
|
-
if(voice.isInRelease)
|
|
160
|
-
{
|
|
161
|
-
// not important
|
|
162
|
-
priority -= 5;
|
|
163
|
-
}
|
|
164
|
-
// less velocity = less important
|
|
165
|
-
priority += voice.velocity / 25; // map to 0-5
|
|
166
|
-
// the newer, more important
|
|
167
|
-
priority -= voice.volumeEnvelopeState;
|
|
168
|
-
if(voice.isInRelease)
|
|
169
|
-
{
|
|
170
|
-
priority -= 5;
|
|
171
|
-
}
|
|
172
|
-
priority -= voice.currentAttenuationDb / 50;
|
|
173
|
-
return priority;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @this {Synthesizer}
|
|
178
|
-
* @param amount {number}
|
|
179
|
-
*/
|
|
180
|
-
export function voiceKilling(amount)
|
|
181
|
-
{
|
|
182
|
-
let allVoices = [];
|
|
183
|
-
for (const channel of this.workletProcessorChannels)
|
|
184
|
-
{
|
|
185
|
-
for (const voice of channel.voices)
|
|
186
|
-
{
|
|
187
|
-
if (!voice.finished)
|
|
188
|
-
{
|
|
189
|
-
const priority = getPriority(channel, voice);
|
|
190
|
-
allVoices.push({ channel, voice, priority });
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Step 2: Sort voices by priority (ascending order)
|
|
196
|
-
allVoices.sort((a, b) => a.priority - b.priority);
|
|
197
|
-
const voicesToRemove = allVoices.slice(0, amount);
|
|
198
|
-
|
|
199
|
-
for (const { channel, voice } of voicesToRemove)
|
|
200
|
-
{
|
|
201
|
-
const index = channel.voices.indexOf(voice);
|
|
202
|
-
if (index > -1)
|
|
203
|
-
{
|
|
204
|
-
channel.voices.splice(index, 1);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Stops the voice
|
|
211
|
-
* @param voice {WorkletVoice} the voice to stop
|
|
212
|
-
* @this {Synthesizer}
|
|
213
|
-
*/
|
|
214
|
-
export function releaseVoice(voice)
|
|
215
|
-
{
|
|
216
|
-
voice.releaseStartTime = this.currentTime;
|
|
217
|
-
// check if the note is shorter than the min note time, if so, extend it
|
|
218
|
-
if(voice.releaseStartTime - voice.startTime < MIN_NOTE_LENGTH)
|
|
219
|
-
{
|
|
220
|
-
voice.releaseStartTime = voice.startTime + MIN_NOTE_LENGTH;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { generatorTypes } from '../../../soundfont/chunk/generators.js'
|
|
2
|
-
import { absCentsToHz, decibelAttenuationToGain } from './unit_converter.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* lowpass_filter.js
|
|
6
|
-
* purpose: applies a low pass filter to a voice
|
|
7
|
-
* note to self: most of this is code is just javascript version of the C code from fluidsynth,
|
|
8
|
-
* they are the real smart guys.
|
|
9
|
-
* Shoutout to them!
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Applies a low-pass filter to the given buffer
|
|
15
|
-
* @param voice {WorkletVoice} the voice we're working on
|
|
16
|
-
* @param outputBuffer {Float32Array} the buffer to apply the filter to
|
|
17
|
-
* @param cutoffCents {number} cutoff frequency in cents
|
|
18
|
-
* @param sampleRate {number} in hertz
|
|
19
|
-
* @this {Synthesizer}
|
|
20
|
-
*/
|
|
21
|
-
export function applyLowpassFilter(voice, outputBuffer, cutoffCents, sampleRate)
|
|
22
|
-
{
|
|
23
|
-
if(cutoffCents > 13499)
|
|
24
|
-
{
|
|
25
|
-
return; // filter is open
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// check if the frequency has changed. if so, calculate new coefficients
|
|
29
|
-
if(voice.filter.cutoffCents !== cutoffCents || voice.filter.reasonanceCb !== voice.modulatedGenerators[generatorTypes.initialFilterQ])
|
|
30
|
-
{
|
|
31
|
-
voice.filter.cutoffCents = cutoffCents;
|
|
32
|
-
voice.filter.reasonanceCb = voice.modulatedGenerators[generatorTypes.initialFilterQ];
|
|
33
|
-
calculateCoefficients(voice, sampleRate);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// filter the input
|
|
37
|
-
for (let i = 0; i < outputBuffer.length; i++) {
|
|
38
|
-
let input = outputBuffer[i];
|
|
39
|
-
let filtered = voice.filter.a0 * input
|
|
40
|
-
+ voice.filter.a1 * voice.filter.x1
|
|
41
|
-
+ voice.filter.a2 * voice.filter.x2
|
|
42
|
-
- voice.filter.a3 * voice.filter.y1
|
|
43
|
-
- voice.filter.a4 * voice.filter.y2;
|
|
44
|
-
|
|
45
|
-
// set buffer
|
|
46
|
-
voice.filter.x2 = voice.filter.x1;
|
|
47
|
-
voice.filter.x1 = input;
|
|
48
|
-
voice.filter.y2 = voice.filter.y1;
|
|
49
|
-
voice.filter.y1 = filtered;
|
|
50
|
-
|
|
51
|
-
outputBuffer[i] = filtered;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param voice {WorkletVoice}
|
|
57
|
-
* @param sampleRate {number}
|
|
58
|
-
*/
|
|
59
|
-
function calculateCoefficients(voice, sampleRate)
|
|
60
|
-
{
|
|
61
|
-
voice.filter.cutoffHz = absCentsToHz(voice.filter.cutoffCents);
|
|
62
|
-
|
|
63
|
-
// fix cutoff on low frequencies (fluid_iir_filter.c line 392)
|
|
64
|
-
if(voice.filter.cutoffHz > 0.45 * sampleRate)
|
|
65
|
-
{
|
|
66
|
-
voice.filter.cutoffHz = 0.45 * sampleRate;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// adjust the filterQ (fluid_iir_filter.c line 204)
|
|
70
|
-
const qDb = (voice.filter.reasonanceCb / 10) - 3.01;
|
|
71
|
-
voice.filter.reasonanceGain = decibelAttenuationToGain(-1 * qDb); // -1 because it's attenuation, and we don't want attenuation
|
|
72
|
-
|
|
73
|
-
// reduce the gain by the Q factor (fluid_iir_filter.c line 250)
|
|
74
|
-
const qGain = 1 / Math.sqrt(voice.filter.reasonanceGain);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// code is ported from https://github.com/sinshu/meltysynth/ to work with js. I'm too dumb to understand the math behind this...
|
|
78
|
-
let w = 2 * Math.PI * voice.filter.cutoffHz / sampleRate;
|
|
79
|
-
let cosw = Math.cos(w);
|
|
80
|
-
let alpha = Math.sin(w) / (2 * voice.filter.reasonanceGain);
|
|
81
|
-
|
|
82
|
-
let b1 = (1 - cosw) * qGain;
|
|
83
|
-
let b0 = b1 / 2;
|
|
84
|
-
let b2 = b0;
|
|
85
|
-
let a0 = 1 + alpha;
|
|
86
|
-
let a1 = -2 * cosw;
|
|
87
|
-
let a2 = 1 - alpha;
|
|
88
|
-
|
|
89
|
-
// set coefficients
|
|
90
|
-
voice.filter.a0 = b0 / a0;
|
|
91
|
-
voice.filter.a1 = b1 / a0;
|
|
92
|
-
voice.filter.a2 = b2 / a0;
|
|
93
|
-
voice.filter.a3 = a1 / a0;
|
|
94
|
-
voice.filter.a4 = a2 / a0;
|
|
95
|
-
}
|
package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { timecentsToSeconds } from './unit_converter.js'
|
|
2
|
-
import { generatorTypes } from '../../../soundfont/chunk/generators.js'
|
|
3
|
-
import { getModulatorCurveValue } from './modulator_curves.js'
|
|
4
|
-
import { modulatorCurveTypes } from '../../../soundfont/chunk/modulators.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* modulation_envelope.js
|
|
8
|
-
* purpose: calculates the modulation envelope for the given voice
|
|
9
|
-
*/
|
|
10
|
-
const 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
|
-
// this makes the db linear ( i think
|
|
16
|
-
CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Calculates the current modulation envelope value for the given time and voice
|
|
21
|
-
* @param voice {WorkletVoice} the voice we're working on
|
|
22
|
-
* @param currentTime {number} in seconds
|
|
23
|
-
* @returns {number} modenv value, from 0 to 1
|
|
24
|
-
*/
|
|
25
|
-
export function getModEnvValue(voice, currentTime)
|
|
26
|
-
{
|
|
27
|
-
// calculate env times
|
|
28
|
-
let attack = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
|
|
29
|
-
let decay = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]));
|
|
30
|
-
let hold = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]));
|
|
31
|
-
|
|
32
|
-
// calculate absolute times
|
|
33
|
-
if(voice.isInRelease && voice.releaseStartTime < currentTime)
|
|
34
|
-
{
|
|
35
|
-
let release = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
|
|
36
|
-
if(voice.modulatedGenerators[generatorTypes.releaseModEnv] < -7199)
|
|
37
|
-
{
|
|
38
|
-
// prevent lowpass bugs if release is instant
|
|
39
|
-
return voice.releaseStartModEnv;
|
|
40
|
-
}
|
|
41
|
-
return (1 - (currentTime - voice.releaseStartTime) / release) * voice.releaseStartModEnv;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let sustain = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
|
|
45
|
-
let delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]) + voice.startTime;
|
|
46
|
-
let attackEnd = attack + delayEnd;
|
|
47
|
-
let holdEnd = hold + attackEnd;
|
|
48
|
-
let decayEnd = decay + holdEnd;
|
|
49
|
-
|
|
50
|
-
let modEnvVal
|
|
51
|
-
if(currentTime < delayEnd)
|
|
52
|
-
{
|
|
53
|
-
modEnvVal = 0; // delay
|
|
54
|
-
}
|
|
55
|
-
else if(currentTime < attackEnd)
|
|
56
|
-
{
|
|
57
|
-
modEnvVal = CONVEX_ATTACK[~~((1 - (attackEnd - currentTime) / attack) * 1000)]; // convex attack
|
|
58
|
-
}
|
|
59
|
-
else if(currentTime < holdEnd)
|
|
60
|
-
{
|
|
61
|
-
modEnvVal = PEAK; // peak
|
|
62
|
-
}
|
|
63
|
-
else if(currentTime < decayEnd)
|
|
64
|
-
{
|
|
65
|
-
modEnvVal = (1 - (decayEnd - currentTime) / decay) * (sustain - PEAK) + PEAK; // decay
|
|
66
|
-
}
|
|
67
|
-
else
|
|
68
|
-
{
|
|
69
|
-
modEnvVal = sustain; // sustain
|
|
70
|
-
}
|
|
71
|
-
voice.currentModEnvValue = modEnvVal;
|
|
72
|
-
return modEnvVal;
|
|
73
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
export const WORKLET_SYSTEM_REVERB_DIVIDER = 200;
|
|
2
|
-
export const WORKLET_SYSTEM_CHORUS_DIVIDER = 500;
|
|
3
|
-
/**
|
|
4
|
-
* stereo_panner.js
|
|
5
|
-
* purpose: pans a given voice out to the stereo output and to the effects' outputs
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Pans the voice to the given output buffers
|
|
10
|
-
* @param gainLeft {number} the left channel gain
|
|
11
|
-
* @param gainRight {number} the right channel gain
|
|
12
|
-
* @param inputBuffer {Float32Array} the input buffer in mono
|
|
13
|
-
* @param output {Float32Array[]} stereo output buffer
|
|
14
|
-
* @param reverb {Float32Array[]} stereo reverb input
|
|
15
|
-
* @param reverbLevel {number} 0 to 1000, the level of reverb to send
|
|
16
|
-
* @param chorus {Float32Array[]} stereo chorus buttfer
|
|
17
|
-
* @param chorusLevel {number} 0 to 1000, the level of chorus to send
|
|
18
|
-
*/
|
|
19
|
-
export function panVoice(gainLeft,
|
|
20
|
-
gainRight,
|
|
21
|
-
inputBuffer,
|
|
22
|
-
output,
|
|
23
|
-
reverb,
|
|
24
|
-
reverbLevel,
|
|
25
|
-
chorus,
|
|
26
|
-
chorusLevel)
|
|
27
|
-
{
|
|
28
|
-
if(isNaN(inputBuffer[0]))
|
|
29
|
-
{
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if(reverbLevel > 0 && reverb !== undefined)
|
|
34
|
-
{
|
|
35
|
-
const reverbLeft = reverb[0];
|
|
36
|
-
const reverbRight = reverb[1];
|
|
37
|
-
// cap reverb
|
|
38
|
-
reverbLevel = Math.min(reverbLevel, 1000);
|
|
39
|
-
const reverbGain = reverbLevel / WORKLET_SYSTEM_REVERB_DIVIDER;
|
|
40
|
-
const reverbLeftGain = gainLeft * reverbGain;
|
|
41
|
-
const reverbRightGain = gainRight * reverbGain;
|
|
42
|
-
for (let i = 0; i < inputBuffer.length; i++) {
|
|
43
|
-
reverbLeft[i] += reverbLeftGain * inputBuffer[i];
|
|
44
|
-
reverbRight[i] += reverbRightGain * inputBuffer[i];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if(chorusLevel > 0 && chorus !== undefined)
|
|
49
|
-
{
|
|
50
|
-
const chorusLeft = chorus[0];
|
|
51
|
-
const chorusRight = chorus[1];
|
|
52
|
-
// cap chorus
|
|
53
|
-
chorusLevel = Math.min(chorusLevel, 1000);
|
|
54
|
-
const chorusGain = chorusLevel / WORKLET_SYSTEM_CHORUS_DIVIDER;
|
|
55
|
-
const chorusLeftGain = gainLeft * chorusGain;
|
|
56
|
-
const chorusRightGain = gainRight * chorusGain;
|
|
57
|
-
for (let i = 0; i < inputBuffer.length; i++) {
|
|
58
|
-
chorusLeft[i] += chorusLeftGain * inputBuffer[i];
|
|
59
|
-
chorusRight[i] += chorusRightGain * inputBuffer[i];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const leftChannel = output[0];
|
|
64
|
-
const rightChannel = output[1];
|
|
65
|
-
if(gainLeft > 0)
|
|
66
|
-
{
|
|
67
|
-
for (let i = 0; i < inputBuffer.length; i++) {
|
|
68
|
-
leftChannel[i] += gainLeft * inputBuffer[i];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if(gainRight > 0) {
|
|
72
|
-
for (let i = 0; i < inputBuffer.length; i++) {
|
|
73
|
-
rightChannel[i] += gainRight * inputBuffer[i];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { decibelAttenuationToGain, timecentsToSeconds } from './unit_converter.js'
|
|
2
|
-
import { generatorTypes } from '../../../soundfont/chunk/generators.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* volume_envelope.js
|
|
6
|
-
* purpose: applies a volume envelope for a given voice
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const DB_SILENCE = 100;
|
|
10
|
-
const GAIN_SILENCE = 0.005;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* VOL ENV STATES:
|
|
14
|
-
* 0 - delay
|
|
15
|
-
* 1 - attack
|
|
16
|
-
* 2 - hold/peak
|
|
17
|
-
* 3 - decay
|
|
18
|
-
* 4 - sustain
|
|
19
|
-
* release is indicated by isInRelease property
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Applies volume envelope gain to the given output buffer
|
|
24
|
-
* @param voice {WorkletVoice} the voice we're working on
|
|
25
|
-
* @param audioBuffer {Float32Array} the audio buffer to modify
|
|
26
|
-
* @param currentTime {number} the current audio time
|
|
27
|
-
* @param centibelOffset {number} the centibel offset of volume, for modLFOtoVolume
|
|
28
|
-
* @param sampleTime {number} single sample time in seconds, usually 1 / 44100 of a second
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
export function applyVolumeEnvelope(voice, audioBuffer, currentTime, centibelOffset, sampleTime)
|
|
32
|
-
{
|
|
33
|
-
// calculate values
|
|
34
|
-
let decibelOffset = centibelOffset / 10;
|
|
35
|
-
|
|
36
|
-
// calculate env times
|
|
37
|
-
let attack = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
|
|
38
|
-
let decay = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayVolEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay]));
|
|
39
|
-
|
|
40
|
-
// calculate absolute times
|
|
41
|
-
let attenuation = voice.modulatedGenerators[generatorTypes.initialAttenuation] / 10; // divide by ten to get decibelts
|
|
42
|
-
let release = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
|
|
43
|
-
let sustain = attenuation + voice.modulatedGenerators[generatorTypes.sustainVolEnv] / 10;
|
|
44
|
-
let delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVolEnv]) + voice.startTime;
|
|
45
|
-
let attackEnd = attack + delayEnd;
|
|
46
|
-
let holdEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdVolEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvHold])) + attackEnd;
|
|
47
|
-
let decayEnd = decay + holdEnd;
|
|
48
|
-
|
|
49
|
-
// check if voice is in release
|
|
50
|
-
if(voice.isInRelease)
|
|
51
|
-
{
|
|
52
|
-
// calculate the db attenuation at the time of release (not a constant because it can change (ex, volume set to 0, the sound should cut off)
|
|
53
|
-
let releaseStartDb;
|
|
54
|
-
switch (voice.volumeEnvelopeState)
|
|
55
|
-
{
|
|
56
|
-
case 0:
|
|
57
|
-
// no sound: fill with zero and skip!
|
|
58
|
-
for (let i = 0; i < audioBuffer.length; i++) {
|
|
59
|
-
audioBuffer[i] = 0;
|
|
60
|
-
}
|
|
61
|
-
return;
|
|
62
|
-
|
|
63
|
-
case 1:
|
|
64
|
-
// attack phase
|
|
65
|
-
// attack is linear (in gain) so we need to do get db from that
|
|
66
|
-
let elapsed = 1 - ((attackEnd - voice.releaseStartTime) / attack);
|
|
67
|
-
// calculate the gain that the attack would have
|
|
68
|
-
let attackGain = elapsed * decibelAttenuationToGain(attenuation + decibelOffset);
|
|
69
|
-
|
|
70
|
-
// turn that into db
|
|
71
|
-
releaseStartDb = 20 * Math.log10(attackGain) * -1;
|
|
72
|
-
break;
|
|
73
|
-
|
|
74
|
-
case 2:
|
|
75
|
-
// hold
|
|
76
|
-
releaseStartDb = attenuation;
|
|
77
|
-
break;
|
|
78
|
-
|
|
79
|
-
case 3:
|
|
80
|
-
// decay
|
|
81
|
-
releaseStartDb = (1 - (decayEnd - voice.releaseStartTime) / decay) * (sustain - attenuation) + attenuation;
|
|
82
|
-
break;
|
|
83
|
-
|
|
84
|
-
case 4:
|
|
85
|
-
// sustain
|
|
86
|
-
releaseStartDb = sustain;
|
|
87
|
-
break;
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// uncommented because doesn't seem to be needed but just in case
|
|
92
|
-
// // if the voice is not released, but state set to true (due to min note length, simply use the release db)
|
|
93
|
-
// if(voice.releaseStartTime > currentTime)
|
|
94
|
-
// {
|
|
95
|
-
// const gain = decibelAttenuationToGain(releaseStartDb + decibelOffset);
|
|
96
|
-
// for (let i = 0; i < audioBuffer.length; i++) {
|
|
97
|
-
// audioBuffer[i] = gain * audioBuffer[i];
|
|
98
|
-
// }
|
|
99
|
-
// return;
|
|
100
|
-
// }
|
|
101
|
-
|
|
102
|
-
let elapsedRelease = currentTime - voice.releaseStartTime;
|
|
103
|
-
let dbDifference = DB_SILENCE - releaseStartDb;
|
|
104
|
-
let gain;
|
|
105
|
-
for (let i = 0; i < audioBuffer.length; i++) {
|
|
106
|
-
let db = (elapsedRelease / release) * dbDifference + releaseStartDb;
|
|
107
|
-
gain = decibelAttenuationToGain(db + decibelOffset);
|
|
108
|
-
audioBuffer[i] = gain * audioBuffer[i];
|
|
109
|
-
elapsedRelease += sampleTime;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if(gain <= GAIN_SILENCE)
|
|
113
|
-
{
|
|
114
|
-
voice.finished = true;
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
let currentFrameTime = currentTime;
|
|
119
|
-
let dbAttenuation;
|
|
120
|
-
for (let i = 0; i < audioBuffer.length; i++) {
|
|
121
|
-
switch(voice.volumeEnvelopeState)
|
|
122
|
-
{
|
|
123
|
-
case 0:
|
|
124
|
-
// delay phase, no sound is produced
|
|
125
|
-
if(currentFrameTime >= delayEnd)
|
|
126
|
-
{
|
|
127
|
-
voice.volumeEnvelopeState++;
|
|
128
|
-
}
|
|
129
|
-
else
|
|
130
|
-
{
|
|
131
|
-
dbAttenuation = DB_SILENCE;
|
|
132
|
-
audioBuffer[i] = 0;
|
|
133
|
-
|
|
134
|
-
// no need to go through the hassle of converting. Skip
|
|
135
|
-
currentFrameTime += sampleTime;
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
// fallthrough
|
|
139
|
-
|
|
140
|
-
case 1:
|
|
141
|
-
// attack phase: ramp from 0 to attenuation
|
|
142
|
-
if(currentFrameTime >= attackEnd)
|
|
143
|
-
{
|
|
144
|
-
voice.volumeEnvelopeState++;
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
// Special case: linear gain ramp instead of linear db ramp
|
|
148
|
-
const elapsed = (attackEnd - currentFrameTime) / attack;
|
|
149
|
-
dbAttenuation = 10 * Math.log10((elapsed * (attenuation - DB_SILENCE) + DB_SILENCE) * -1);
|
|
150
|
-
audioBuffer[i] = audioBuffer[i] * (1 - elapsed) * decibelAttenuationToGain(attenuation + decibelOffset);
|
|
151
|
-
currentFrameTime += sampleTime;
|
|
152
|
-
continue
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
// fallthrough
|
|
156
|
-
|
|
157
|
-
case 2:
|
|
158
|
-
// hold/peak phase: stay at attenuation
|
|
159
|
-
if(currentFrameTime >= holdEnd)
|
|
160
|
-
{
|
|
161
|
-
voice.volumeEnvelopeState++;
|
|
162
|
-
}
|
|
163
|
-
else
|
|
164
|
-
{
|
|
165
|
-
dbAttenuation = attenuation;
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
// fallthrough
|
|
169
|
-
|
|
170
|
-
case 3:
|
|
171
|
-
// decay phase: linear ramp from attenuation to sustain
|
|
172
|
-
if(currentFrameTime >= decayEnd)
|
|
173
|
-
{
|
|
174
|
-
voice.volumeEnvelopeState++;
|
|
175
|
-
}
|
|
176
|
-
else
|
|
177
|
-
{
|
|
178
|
-
dbAttenuation = (1 - (decayEnd - currentFrameTime) / decay) * (sustain - attenuation) + attenuation;
|
|
179
|
-
break
|
|
180
|
-
}
|
|
181
|
-
// fallthrough
|
|
182
|
-
|
|
183
|
-
case 4:
|
|
184
|
-
// sustain phase: stay at sustain
|
|
185
|
-
dbAttenuation = sustain;
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
// apply gain and advance the time
|
|
189
|
-
const gain = decibelAttenuationToGain(dbAttenuation + decibelOffset);
|
|
190
|
-
audioBuffer[i] = audioBuffer[i] * gain;
|
|
191
|
-
currentFrameTime += sampleTime;
|
|
192
|
-
}
|
|
193
|
-
voice.currentAttenuationDb = dbAttenuation;
|
|
194
|
-
}
|