spessasynth_core 1.1.3 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +3 -26
- package/README.md +156 -474
- package/index.js +73 -8
- package/package.json +21 -8
- package/src/externals/fflate/LICENSE +21 -0
- package/src/externals/fflate/fflate.min.js +1 -0
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/src/externals/stbvorbis_sync/LICENSE +202 -0
- package/src/externals/stbvorbis_sync/NOTICE +6 -0
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/src/midi/README.md +32 -0
- package/src/midi/basic_midi.js +567 -0
- package/src/midi/midi_builder.js +202 -0
- package/src/midi/midi_loader.js +324 -0
- package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
- package/src/midi/midi_sequence.js +224 -0
- package/src/midi/midi_tools/get_note_times.js +154 -0
- package/src/midi/midi_tools/midi_editor.js +611 -0
- package/src/midi/midi_tools/midi_writer.js +99 -0
- package/src/midi/midi_tools/rmidi_writer.js +567 -0
- package/src/midi/midi_tools/used_keys_loaded.js +238 -0
- package/src/midi/xmf_loader.js +454 -0
- package/src/sequencer/README.md +5 -0
- package/src/sequencer/events.js +81 -0
- package/src/sequencer/play.js +349 -0
- package/src/sequencer/process_event.js +165 -0
- package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
- package/src/sequencer/sequencer_engine.js +367 -0
- package/src/sequencer/song_control.js +201 -0
- package/src/soundfont/README.md +13 -0
- package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
- package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
- package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
- package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
- package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
- package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
- package/src/soundfont/basic_soundfont/generator.js +220 -0
- package/src/soundfont/basic_soundfont/modulator.js +378 -0
- package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
- package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
- package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
- package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
- package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
- package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
- package/src/soundfont/dls/articulator_converter.js +396 -0
- package/src/soundfont/dls/dls_destinations.js +38 -0
- package/src/soundfont/dls/dls_preset.js +44 -0
- package/src/soundfont/dls/dls_sample.js +75 -0
- package/src/soundfont/dls/dls_soundfont.js +186 -0
- package/src/soundfont/dls/dls_sources.js +62 -0
- package/src/soundfont/dls/dls_zone.js +95 -0
- package/src/soundfont/dls/read_articulation.js +299 -0
- package/src/soundfont/dls/read_instrument.js +121 -0
- package/src/soundfont/dls/read_instrument_list.js +17 -0
- package/src/soundfont/dls/read_lart.js +35 -0
- package/src/soundfont/dls/read_region.js +152 -0
- package/src/soundfont/dls/read_samples.js +270 -0
- package/src/soundfont/load_soundfont.js +21 -0
- package/src/soundfont/read_sf2/generators.js +46 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
- package/src/soundfont/read_sf2/modulators.js +36 -0
- package/src/soundfont/read_sf2/presets.js +80 -0
- package/src/soundfont/read_sf2/samples.js +304 -0
- package/src/soundfont/read_sf2/soundfont.js +305 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
- package/src/synthetizer/README.md +7 -0
- package/src/synthetizer/audio_engine/README.md +9 -0
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
- package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
- package/src/synthetizer/audio_engine/main_processor.js +804 -0
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
- package/src/synthetizer/synth_constants.js +22 -0
- package/{spessasynth_core → src}/utils/README.md +1 -0
- package/src/utils/buffer_to_wav.js +185 -0
- package/src/utils/byte_functions/big_endian.js +32 -0
- package/src/utils/byte_functions/little_endian.js +77 -0
- package/src/utils/byte_functions/string.js +107 -0
- package/src/utils/byte_functions/variable_length_quantity.js +42 -0
- package/src/utils/fill_with_defaults.js +21 -0
- package/src/utils/indexed_array.js +52 -0
- package/{spessasynth_core → src}/utils/loggin.js +70 -78
- package/src/utils/other.js +92 -0
- package/src/utils/sysex_detector.js +58 -0
- package/src/utils/xg_hacks.js +193 -0
- package/.idea/inspectionProfiles/Project_Default.xml +0 -10
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/spessasynth_core.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/spessasynth_core/midi_parser/README.md +0 -3
- package/spessasynth_core/midi_parser/midi_loader.js +0 -386
- package/spessasynth_core/sequencer/sequencer.js +0 -202
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
- package/spessasynth_core/soundfont/README.md +0 -4
- package/spessasynth_core/soundfont/chunk/generators.js +0 -205
- package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
- package/spessasynth_core/soundfont/chunk/presets.js +0 -264
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
- package/spessasynth_core/soundfont/chunk/samples.js +0 -250
- package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
- package/spessasynth_core/synthetizer/README.md +0 -6
- package/spessasynth_core/synthetizer/synthesizer.js +0 -313
- package/spessasynth_core/synthetizer/worklet_system/README.md +0 -3
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -290
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -280
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +0 -102
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +0 -77
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +0 -140
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -266
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -104
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +0 -223
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -133
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +0 -73
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -76
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -272
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -83
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -175
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +0 -106
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +0 -285
- package/spessasynth_core/utils/buffer_to_wav.js +0 -70
- package/spessasynth_core/utils/byte_functions.js +0 -141
- package/spessasynth_core/utils/other.js +0 -49
- package/spessasynth_core/utils/shiftable_array.js +0 -26
- package/spessasynth_core/utils/stbvorbis_sync.js +0 -1877
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { generatorTypes } from '../../../soundfont/chunk/generators.js'
|
|
2
|
-
import {absCentsToHz, decibelAttenuationToGain, 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, recalculateVolumeEnvelope} 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
|
-
const HALF_PI = Math.PI / 2;
|
|
13
|
-
export const PAN_SMOOTHING_FACTOR = 0.01;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Renders a voice to the stereo output buffer
|
|
17
|
-
* @param channel {WorkletProcessorChannel} the voice's channel
|
|
18
|
-
* @param voice {WorkletVoice} the voice to render
|
|
19
|
-
* @param output {Float32Array[]} the output buffer
|
|
20
|
-
* @param reverbOutput {Float32Array[]} output for reverb
|
|
21
|
-
* @param chorusOutput {Float32Array[]} output for chorus
|
|
22
|
-
* @this {Synthesizer}
|
|
23
|
-
*/
|
|
24
|
-
export function renderVoice(channel, voice, output, reverbOutput, chorusOutput)
|
|
25
|
-
{
|
|
26
|
-
// check if release
|
|
27
|
-
if(!voice.isInRelease)
|
|
28
|
-
{
|
|
29
|
-
// if not in release, check if the release time is
|
|
30
|
-
if (this.currentTime >= voice.releaseStartTime)
|
|
31
|
-
{
|
|
32
|
-
voice.releaseStartModEnv = voice.currentModEnvValue;
|
|
33
|
-
voice.isInRelease = true;
|
|
34
|
-
recalculateVolumeEnvelope(voice);
|
|
35
|
-
voice.volumeEnvelope.currentReleaseGain = decibelAttenuationToGain(voice.volumeEnvelope.releaseStartDb);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// if the initial attenuation is more than 100dB, skip the voice (it's silent anyway)
|
|
41
|
-
if(voice.modulatedGenerators[generatorTypes.initialAttenuation] > 2500)
|
|
42
|
-
{
|
|
43
|
-
if(voice.isInRelease)
|
|
44
|
-
{
|
|
45
|
-
voice.finished = true;
|
|
46
|
-
}
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// TUNING
|
|
51
|
-
|
|
52
|
-
// calculate tuning
|
|
53
|
-
let cents = voice.modulatedGenerators[generatorTypes.fineTune]
|
|
54
|
-
+ channel.customControllers[customControllers.channelTuning]
|
|
55
|
-
+ channel.customControllers[customControllers.channelTranspose]
|
|
56
|
-
+ channel.customControllers[customControllers.masterTuning];
|
|
57
|
-
let semitones = voice.modulatedGenerators[generatorTypes.coarseTune];
|
|
58
|
-
|
|
59
|
-
// calculate tuning by key
|
|
60
|
-
cents += (voice.targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
|
|
61
|
-
|
|
62
|
-
// vibrato LFO
|
|
63
|
-
const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
|
|
64
|
-
if(vibratoDepth > 0)
|
|
65
|
-
{
|
|
66
|
-
const vibStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVibLFO]);
|
|
67
|
-
const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
|
|
68
|
-
const lfoVal = getLFOValue(vibStart, vibFreqHz, this.currentTime);
|
|
69
|
-
if(lfoVal)
|
|
70
|
-
{
|
|
71
|
-
cents += lfoVal * (vibratoDepth * channel.customControllers[customControllers.modulationMultiplier]);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// lowpass frequency
|
|
76
|
-
let lowpassCents = voice.modulatedGenerators[generatorTypes.initialFilterFc];
|
|
77
|
-
|
|
78
|
-
// mod LFO
|
|
79
|
-
const modPitchDepth = voice.modulatedGenerators[generatorTypes.modLfoToPitch];
|
|
80
|
-
const modVolDepth = voice.modulatedGenerators[generatorTypes.modLfoToVolume];
|
|
81
|
-
const modFilterDepth = voice.modulatedGenerators[generatorTypes.modLfoToFilterFc];
|
|
82
|
-
let modLfoCentibels = 0;
|
|
83
|
-
if(modPitchDepth + modFilterDepth + modVolDepth > 0)
|
|
84
|
-
{
|
|
85
|
-
const modStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModLFO]);
|
|
86
|
-
const modFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqModLFO]);
|
|
87
|
-
const modLfoValue = getLFOValue(modStart, modFreqHz, this.currentTime);
|
|
88
|
-
cents += modLfoValue * (modPitchDepth * channel.customControllers[customControllers.modulationMultiplier]);
|
|
89
|
-
modLfoCentibels = modLfoValue * modVolDepth;
|
|
90
|
-
lowpassCents += modLfoValue * modFilterDepth;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// channel vibrato (GS NRPN)
|
|
94
|
-
if(channel.channelVibrato.depth > 0)
|
|
95
|
-
{
|
|
96
|
-
const channelVibrato = getLFOValue(voice.startTime + channel.channelVibrato.delay, channel.channelVibrato.rate, this.currentTime);
|
|
97
|
-
if(channelVibrato)
|
|
98
|
-
{
|
|
99
|
-
cents += channelVibrato * channel.channelVibrato.depth;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// mod env
|
|
104
|
-
const modEnvPitchDepth = voice.modulatedGenerators[generatorTypes.modEnvToPitch];
|
|
105
|
-
const modEnvFilterDepth = voice.modulatedGenerators[generatorTypes.modEnvToFilterFc];
|
|
106
|
-
const modEnv = getModEnvValue(voice, this.currentTime);
|
|
107
|
-
lowpassCents += modEnv * modEnvFilterDepth;
|
|
108
|
-
cents += modEnv * modEnvPitchDepth;
|
|
109
|
-
|
|
110
|
-
// finally calculate the playback rate
|
|
111
|
-
const centsTotal = ~~(cents + semitones * 100);
|
|
112
|
-
if(centsTotal !== voice.currentTuningCents)
|
|
113
|
-
{
|
|
114
|
-
voice.currentTuningCents = centsTotal;
|
|
115
|
-
voice.currentTuningCalculated = Math.pow(2, centsTotal / 1200);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// PANNING
|
|
119
|
-
const pan = ( (Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan] )) + 500) / 1000) ; // 0 to 1
|
|
120
|
-
|
|
121
|
-
// SYNTHESIS
|
|
122
|
-
const bufferOut = new Float32Array(output[0].length);
|
|
123
|
-
|
|
124
|
-
// wavetable oscillator
|
|
125
|
-
getOscillatorData(voice, this.workletDumpedSamplesList[voice.sample.sampleID], bufferOut);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// lowpass filter
|
|
129
|
-
applyLowpassFilter(voice, bufferOut, lowpassCents, this.sampleRate);
|
|
130
|
-
|
|
131
|
-
// volenv
|
|
132
|
-
applyVolumeEnvelope(voice, bufferOut, this.currentTime, modLfoCentibels, this.sampleTime, this.volumeEnvelopeSmoothingFactor);
|
|
133
|
-
|
|
134
|
-
// pan the voice and write out
|
|
135
|
-
voice.currentPan += (pan - voice.currentPan) * this.panSmoothingFactor; // smooth out pan to prevent clicking
|
|
136
|
-
const panLeft = Math.cos(HALF_PI * voice.currentPan) * this.panLeft;
|
|
137
|
-
const panRight = Math.sin(HALF_PI * voice.currentPan) * this.panRight;
|
|
138
|
-
panVoice(
|
|
139
|
-
panLeft,
|
|
140
|
-
panRight,
|
|
141
|
-
bufferOut,
|
|
142
|
-
output,
|
|
143
|
-
reverbOutput, voice.modulatedGenerators[generatorTypes.reverbEffectsSend],
|
|
144
|
-
chorusOutput, voice.modulatedGenerators[generatorTypes.chorusEffectsSend]);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @param channel {WorkletProcessorChannel}
|
|
149
|
-
* @param voice {WorkletVoice}
|
|
150
|
-
* @return {number}
|
|
151
|
-
*/
|
|
152
|
-
function getPriority(channel, voice)
|
|
153
|
-
{
|
|
154
|
-
let priority = 0;
|
|
155
|
-
if(channel.drumChannel)
|
|
156
|
-
{
|
|
157
|
-
// important
|
|
158
|
-
priority += 5;
|
|
159
|
-
}
|
|
160
|
-
if(voice.isInRelease)
|
|
161
|
-
{
|
|
162
|
-
// not important
|
|
163
|
-
priority -= 5;
|
|
164
|
-
}
|
|
165
|
-
// less velocity = less important
|
|
166
|
-
priority += voice.velocity / 25; // map to 0-5
|
|
167
|
-
// the newer, more important
|
|
168
|
-
priority -= voice.volumeEnvelope.state;
|
|
169
|
-
if(voice.isInRelease)
|
|
170
|
-
{
|
|
171
|
-
priority -= 5;
|
|
172
|
-
}
|
|
173
|
-
priority -= voice.volumeEnvelope.currentAttenuationDb / 50;
|
|
174
|
-
return priority;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* @this {Synthesizer}
|
|
179
|
-
* @param amount {number}
|
|
180
|
-
*/
|
|
181
|
-
export function voiceKilling(amount)
|
|
182
|
-
{
|
|
183
|
-
let allVoices = [];
|
|
184
|
-
for (const channel of this.workletProcessorChannels)
|
|
185
|
-
{
|
|
186
|
-
for (const voice of channel.voices)
|
|
187
|
-
{
|
|
188
|
-
if (!voice.finished)
|
|
189
|
-
{
|
|
190
|
-
const priority = getPriority(channel, voice);
|
|
191
|
-
allVoices.push({ channel, voice, priority });
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Step 2: Sort voices by priority (ascending order)
|
|
197
|
-
allVoices.sort((a, b) => a.priority - b.priority);
|
|
198
|
-
const voicesToRemove = allVoices.slice(0, amount);
|
|
199
|
-
|
|
200
|
-
for (const { channel, voice } of voicesToRemove)
|
|
201
|
-
{
|
|
202
|
-
const index = channel.voices.indexOf(voice);
|
|
203
|
-
if (index > -1)
|
|
204
|
-
{
|
|
205
|
-
channel.voices.splice(index, 1);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Stops the voice
|
|
212
|
-
* @param voice {WorkletVoice} the voice to stop
|
|
213
|
-
* @this {Synthesizer}
|
|
214
|
-
*/
|
|
215
|
-
export function releaseVoice(voice)
|
|
216
|
-
{
|
|
217
|
-
voice.releaseStartTime = this.currentTime;
|
|
218
|
-
// check if the note is shorter than the min note time, if so, extend it
|
|
219
|
-
if(voice.releaseStartTime - voice.startTime < MIN_NOTE_LENGTH)
|
|
220
|
-
{
|
|
221
|
-
voice.releaseStartTime = voice.startTime + MIN_NOTE_LENGTH;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
@@ -1,133 +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
|
-
* @typedef {Object} WorkletLowpassFilter
|
|
14
|
-
* @property {number} a0 - filter coefficient 1
|
|
15
|
-
* @property {number} a1 - filter coefficient 2
|
|
16
|
-
* @property {number} a2 - filter coefficient 3
|
|
17
|
-
* @property {number} a3 - filter coefficient 4
|
|
18
|
-
* @property {number} a4 - filter coefficient 5
|
|
19
|
-
* @property {number} x1 - input history 1
|
|
20
|
-
* @property {number} x2 - input history 2
|
|
21
|
-
* @property {number} y1 - output history 1
|
|
22
|
-
* @property {number} y2 - output history 2
|
|
23
|
-
* @property {number} reasonanceCb - reasonance in centibels
|
|
24
|
-
* @property {number} reasonanceGain - resonance gain
|
|
25
|
-
* @property {number} cutoffCents - cutoff frequency in cents
|
|
26
|
-
* @property {number} cutoffHz - cutoff frequency in Hz
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @type {WorkletLowpassFilter}
|
|
31
|
-
*/
|
|
32
|
-
export const DEFAULT_WORKLET_LOWPASS_FILTER = {
|
|
33
|
-
a0: 0,
|
|
34
|
-
a1: 0,
|
|
35
|
-
a2: 0,
|
|
36
|
-
a3: 0,
|
|
37
|
-
a4: 0,
|
|
38
|
-
|
|
39
|
-
x1: 0,
|
|
40
|
-
x2: 0,
|
|
41
|
-
y1: 0,
|
|
42
|
-
y2: 0,
|
|
43
|
-
|
|
44
|
-
reasonanceCb: 0,
|
|
45
|
-
reasonanceGain: 1,
|
|
46
|
-
cutoffCents: 13500,
|
|
47
|
-
cutoffHz: 20000
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Applies a low-pass filter to the given buffer
|
|
53
|
-
* @param voice {WorkletVoice} the voice we're working on
|
|
54
|
-
* @param outputBuffer {Float32Array} the buffer to apply the filter to
|
|
55
|
-
* @param cutoffCents {number} cutoff frequency in cents
|
|
56
|
-
* @param sampleRate {number} in hertz
|
|
57
|
-
* @this {Synthesizer}
|
|
58
|
-
*/
|
|
59
|
-
export function applyLowpassFilter(voice, outputBuffer, cutoffCents, sampleRate)
|
|
60
|
-
{
|
|
61
|
-
if(cutoffCents > 13499)
|
|
62
|
-
{
|
|
63
|
-
return; // filter is open
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// check if the frequency has changed. if so, calculate new coefficients
|
|
67
|
-
if(voice.filter.cutoffCents !== cutoffCents || voice.filter.reasonanceCb !== voice.modulatedGenerators[generatorTypes.initialFilterQ])
|
|
68
|
-
{
|
|
69
|
-
voice.filter.cutoffCents = cutoffCents;
|
|
70
|
-
voice.filter.reasonanceCb = voice.modulatedGenerators[generatorTypes.initialFilterQ];
|
|
71
|
-
calculateCoefficients(voice, sampleRate);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// filter the input
|
|
75
|
-
for (let i = 0; i < outputBuffer.length; i++) {
|
|
76
|
-
let input = outputBuffer[i];
|
|
77
|
-
let filtered = voice.filter.a0 * input
|
|
78
|
-
+ voice.filter.a1 * voice.filter.x1
|
|
79
|
-
+ voice.filter.a2 * voice.filter.x2
|
|
80
|
-
- voice.filter.a3 * voice.filter.y1
|
|
81
|
-
- voice.filter.a4 * voice.filter.y2;
|
|
82
|
-
|
|
83
|
-
// set buffer
|
|
84
|
-
voice.filter.x2 = voice.filter.x1;
|
|
85
|
-
voice.filter.x1 = input;
|
|
86
|
-
voice.filter.y2 = voice.filter.y1;
|
|
87
|
-
voice.filter.y1 = filtered;
|
|
88
|
-
|
|
89
|
-
outputBuffer[i] = filtered;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param voice {WorkletVoice}
|
|
95
|
-
* @param sampleRate {number}
|
|
96
|
-
*/
|
|
97
|
-
function calculateCoefficients(voice, sampleRate)
|
|
98
|
-
{
|
|
99
|
-
voice.filter.cutoffHz = absCentsToHz(voice.filter.cutoffCents);
|
|
100
|
-
|
|
101
|
-
// fix cutoff on low frequencies (fluid_iir_filter.c line 392)
|
|
102
|
-
if(voice.filter.cutoffHz > 0.45 * sampleRate)
|
|
103
|
-
{
|
|
104
|
-
voice.filter.cutoffHz = 0.45 * sampleRate;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// adjust the filterQ (fluid_iir_filter.c line 204)
|
|
108
|
-
const qDb = (voice.filter.reasonanceCb / 10) - 3.01;
|
|
109
|
-
voice.filter.reasonanceGain = decibelAttenuationToGain(-1 * qDb); // -1 because it's attenuation, and we don't want attenuation
|
|
110
|
-
|
|
111
|
-
// reduce the gain by the Q factor (fluid_iir_filter.c line 250)
|
|
112
|
-
const qGain = 1 / Math.sqrt(voice.filter.reasonanceGain);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// code is ported from https://github.com/sinshu/meltysynth/ to work with js. I'm too dumb to understand the math behind this...
|
|
116
|
-
let w = 2 * Math.PI * voice.filter.cutoffHz / sampleRate;
|
|
117
|
-
let cosw = Math.cos(w);
|
|
118
|
-
let alpha = Math.sin(w) / (2 * voice.filter.reasonanceGain);
|
|
119
|
-
|
|
120
|
-
let b1 = (1 - cosw) * qGain;
|
|
121
|
-
let b0 = b1 / 2;
|
|
122
|
-
let b2 = b0;
|
|
123
|
-
let a0 = 1 + alpha;
|
|
124
|
-
let a1 = -2 * cosw;
|
|
125
|
-
let a2 = 1 - alpha;
|
|
126
|
-
|
|
127
|
-
// set coefficients
|
|
128
|
-
voice.filter.a0 = b0 / a0;
|
|
129
|
-
voice.filter.a1 = b1 / a0;
|
|
130
|
-
voice.filter.a2 = b2 / a0;
|
|
131
|
-
voice.filter.a3 = a1 / a0;
|
|
132
|
-
voice.filter.a4 = a2 / a0;
|
|
133
|
-
}
|
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
|
-
}
|