spessasynth_lib 3.25.21 → 3.25.22
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/index.js +7 -7
- package/{midi_parser → midi}/basic_midi.js +6 -4
- package/{midi_parser → midi}/midi_loader.js +1 -1
- package/midi/midi_tools/get_note_times.js +154 -0
- package/{midi_parser → midi/midi_tools}/midi_editor.js +8 -8
- package/{midi_parser → midi/midi_tools}/midi_writer.js +3 -3
- package/{midi_parser → midi/midi_tools}/rmidi_writer.js +10 -10
- package/{midi_parser → midi/midi_tools}/used_keys_loaded.js +7 -7
- package/{midi_parser → midi}/xmf_loader.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer_engine/events.js +1 -1
- package/sequencer/sequencer_engine/play.js +3 -5
- package/sequencer/sequencer_engine/process_event.js +1 -1
- package/sequencer/sequencer_engine/sequencer_engine.js +1 -1
- package/sequencer/sequencer_engine/song_control.js +3 -3
- package/sequencer/worklet_wrapper/sequencer.js +3 -3
- package/soundfont/basic_soundfont/modulator.js +1 -1
- package/soundfont/basic_soundfont/write_dls/modulator_converter.js +1 -1
- package/soundfont/dls/articulator_converter.js +1 -1
- package/synthetizer/audio_engine/README.md +5 -5
- package/synthetizer/audio_engine/{worklet_utilities/worklet_modulator.js → engine_components/compute_modulator.js} +12 -12
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/controller_tables.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods/worklet_key_modifier.js → engine_components/key_modifier_manager.js} +24 -32
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/lowpass_filter.js +1 -1
- package/synthetizer/audio_engine/{worklet_utilities/worklet_processor_channel.js → engine_components/midi_audio_channel.js} +43 -43
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/modulation_envelope.js +6 -6
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/stereo_panner.js +3 -3
- package/synthetizer/audio_engine/{worklet_utilities/worklet_voice.js → engine_components/voice.js} +43 -37
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/volume_envelope.js +17 -17
- package/synthetizer/audio_engine/{worklet_utilities → engine_components}/wavetable_oscillator.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/controller_control/controller_change.js +4 -4
- package/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/controller_control/reset_controllers.js +6 -6
- package/synthetizer/audio_engine/{worklet_methods/create_worklet_channel.js → engine_methods/create_midi_channel.js} +4 -4
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/data_entry/data_entry_coarse.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/data_entry/data_entry_fine.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/mute_channel.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/note_on.js +10 -4
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/program_change.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/render_voice.js +13 -13
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/kill_note.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/note_off.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/stop_all_notes.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/voice_killing.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/system_exclusive.js +4 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/channel_pressure.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/pitch_wheel.js +3 -3
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/poly_pressure.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_master_tuning.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_modulation_depth.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_octave_tuning.js +1 -1
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/set_tuning.js +2 -2
- package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/transpose_channel.js +3 -3
- package/synthetizer/audio_engine/main_processor.js +49 -44
- package/synthetizer/audio_engine/message_protocol/worklet_message.js +1 -12
- package/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +3 -2
- package/synthetizer/worklet_processor.min.js +12 -12
- package/synthetizer/worklet_wrapper/key_modifier_manager.js +6 -4
- package/synthetizer/worklet_wrapper/synth_soundfont_manager.js +1 -1
- package/synthetizer/worklet_wrapper/synthetizer.js +6 -6
- package/synthetizer/worklet_wrapper/worklet_processor.js +26 -24
- package/synthetizer/audio_engine/worklet_methods/controller_control/master_parameters.js +0 -36
- /package/{midi_parser → midi}/README.md +0 -0
- /package/{midi_parser → midi}/midi_builder.js +0 -0
- /package/{midi_parser → midi}/midi_data.js +0 -0
- /package/{midi_parser → midi}/midi_message.js +0 -0
- /package/{midi_parser → midi}/midi_sequence.js +0 -0
- /package/synthetizer/audio_engine/{worklet_utilities → engine_components}/lfo.js +0 -0
- /package/synthetizer/audio_engine/{worklet_utilities → engine_components}/modulator_curves.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods/worklet_soundfont_manager → engine_components/soundfont_manager}/sfman_message.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js → engine_components/soundfont_manager/soundfont_manager.js} +0 -0
- /package/synthetizer/audio_engine/{worklet_utilities → engine_components}/unit_converter.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/portamento_time.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/clear_sound_font.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/get_preset.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/reload_sound_font.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/send_preset_list.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/soundfont_management/set_embedded_sound_font.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/stopping_notes/stop_all_channels.js +0 -0
- /package/synthetizer/audio_engine/{worklet_methods → engine_methods}/tuning_control/transpose_all_channels.js +0 -0
|
@@ -9,25 +9,25 @@ import {
|
|
|
9
9
|
resetControllers,
|
|
10
10
|
resetControllersRP15Compliant,
|
|
11
11
|
resetParameters
|
|
12
|
-
} from "../
|
|
13
|
-
import { renderVoice } from "../
|
|
12
|
+
} from "../engine_methods/controller_control/reset_controllers.js";
|
|
13
|
+
import { renderVoice } from "../engine_methods/render_voice.js";
|
|
14
14
|
import { panVoice } from "./stereo_panner.js";
|
|
15
|
-
import { killNote } from "../
|
|
16
|
-
import { setTuning } from "../
|
|
17
|
-
import { setModulationDepth } from "../
|
|
18
|
-
import { dataEntryFine } from "../
|
|
19
|
-
import { controllerChange } from "../
|
|
20
|
-
import { stopAllNotes } from "../
|
|
21
|
-
import { muteChannel } from "../
|
|
22
|
-
import { transposeChannel } from "../
|
|
23
|
-
import { dataEntryCoarse } from "../
|
|
24
|
-
import { noteOn } from "../
|
|
25
|
-
import { noteOff } from "../
|
|
26
|
-
import { polyPressure } from "../
|
|
27
|
-
import { channelPressure } from "../
|
|
28
|
-
import { pitchWheel } from "../
|
|
29
|
-
import { setOctaveTuning } from "../
|
|
30
|
-
import { programChange } from "../
|
|
15
|
+
import { killNote } from "../engine_methods/stopping_notes/kill_note.js";
|
|
16
|
+
import { setTuning } from "../engine_methods/tuning_control/set_tuning.js";
|
|
17
|
+
import { setModulationDepth } from "../engine_methods/tuning_control/set_modulation_depth.js";
|
|
18
|
+
import { dataEntryFine } from "../engine_methods/data_entry/data_entry_fine.js";
|
|
19
|
+
import { controllerChange } from "../engine_methods/controller_control/controller_change.js";
|
|
20
|
+
import { stopAllNotes } from "../engine_methods/stopping_notes/stop_all_notes.js";
|
|
21
|
+
import { muteChannel } from "../engine_methods/mute_channel.js";
|
|
22
|
+
import { transposeChannel } from "../engine_methods/tuning_control/transpose_channel.js";
|
|
23
|
+
import { dataEntryCoarse } from "../engine_methods/data_entry/data_entry_coarse.js";
|
|
24
|
+
import { noteOn } from "../engine_methods/note_on.js";
|
|
25
|
+
import { noteOff } from "../engine_methods/stopping_notes/note_off.js";
|
|
26
|
+
import { polyPressure } from "../engine_methods/tuning_control/poly_pressure.js";
|
|
27
|
+
import { channelPressure } from "../engine_methods/tuning_control/channel_pressure.js";
|
|
28
|
+
import { pitchWheel } from "../engine_methods/tuning_control/pitch_wheel.js";
|
|
29
|
+
import { setOctaveTuning } from "../engine_methods/tuning_control/set_octave_tuning.js";
|
|
30
|
+
import { programChange } from "../engine_methods/program_change.js";
|
|
31
31
|
import { chooseBank, isSystemXG, parseBankSelect } from "../../../utils/xg_hacks.js";
|
|
32
32
|
import { DEFAULT_PERCUSSION } from "../../synth_constants.js";
|
|
33
33
|
import { modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
@@ -36,7 +36,7 @@ import { returnMessageType } from "../message_protocol/worklet_message.js";
|
|
|
36
36
|
/**
|
|
37
37
|
* This class represents a single MIDI Channel within the synthesizer.
|
|
38
38
|
*/
|
|
39
|
-
class
|
|
39
|
+
class MidiAudioChannel
|
|
40
40
|
{
|
|
41
41
|
/**
|
|
42
42
|
* An array of MIDI controller values and values used by modulators as the source (e.g., pitch bend, bend range, etc.).
|
|
@@ -177,13 +177,13 @@ class WorkletProcessorChannel
|
|
|
177
177
|
|
|
178
178
|
/**
|
|
179
179
|
* An array of voices currently active on the channel.
|
|
180
|
-
* @type {
|
|
180
|
+
* @type {Voice[]}
|
|
181
181
|
*/
|
|
182
182
|
voices = [];
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* An array of voices that are sustained on the channel.
|
|
186
|
-
* @type {
|
|
186
|
+
* @type {Voice[]}
|
|
187
187
|
*/
|
|
188
188
|
sustainedVoices = [];
|
|
189
189
|
|
|
@@ -440,32 +440,32 @@ class WorkletProcessorChannel
|
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
// voice
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
443
|
+
MidiAudioChannel.prototype.renderVoice = renderVoice;
|
|
444
|
+
MidiAudioChannel.prototype.panVoice = panVoice;
|
|
445
|
+
MidiAudioChannel.prototype.killNote = killNote;
|
|
446
|
+
MidiAudioChannel.prototype.stopAllNotes = stopAllNotes;
|
|
447
|
+
MidiAudioChannel.prototype.muteChannel = muteChannel;
|
|
448
448
|
|
|
449
449
|
// MIDI messages
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
450
|
+
MidiAudioChannel.prototype.noteOn = noteOn;
|
|
451
|
+
MidiAudioChannel.prototype.noteOff = noteOff;
|
|
452
|
+
MidiAudioChannel.prototype.polyPressure = polyPressure;
|
|
453
|
+
MidiAudioChannel.prototype.channelPressure = channelPressure;
|
|
454
|
+
MidiAudioChannel.prototype.pitchWheel = pitchWheel;
|
|
455
|
+
MidiAudioChannel.prototype.programChange = programChange;
|
|
456
456
|
|
|
457
457
|
// Tuning
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
458
|
+
MidiAudioChannel.prototype.setTuning = setTuning;
|
|
459
|
+
MidiAudioChannel.prototype.setOctaveTuning = setOctaveTuning;
|
|
460
|
+
MidiAudioChannel.prototype.setModulationDepth = setModulationDepth;
|
|
461
|
+
MidiAudioChannel.prototype.transposeChannel = transposeChannel;
|
|
462
462
|
|
|
463
463
|
// CC
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
464
|
+
MidiAudioChannel.prototype.controllerChange = controllerChange;
|
|
465
|
+
MidiAudioChannel.prototype.resetControllers = resetControllers;
|
|
466
|
+
MidiAudioChannel.prototype.resetControllersRP15Compliant = resetControllersRP15Compliant;
|
|
467
|
+
MidiAudioChannel.prototype.resetParameters = resetParameters;
|
|
468
|
+
MidiAudioChannel.prototype.dataEntryFine = dataEntryFine;
|
|
469
|
+
MidiAudioChannel.prototype.dataEntryCoarse = dataEntryCoarse;
|
|
470
470
|
|
|
471
|
-
export {
|
|
471
|
+
export { MidiAudioChannel };
|
package/synthetizer/audio_engine/{worklet_utilities → engine_components}/modulation_envelope.js
RENAMED
|
@@ -17,7 +17,7 @@ for (let i = 0; i < CONVEX_ATTACK.length; i++)
|
|
|
17
17
|
CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export class
|
|
20
|
+
export class ModulationEnvelope
|
|
21
21
|
{
|
|
22
22
|
/**
|
|
23
23
|
* The attack duration, in seconds
|
|
@@ -83,15 +83,15 @@ export class WorkletModulationEnvelope
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Starts the release phase in the envelope
|
|
86
|
-
* @param voice {
|
|
86
|
+
* @param voice {Voice} the voice this envelope belongs to
|
|
87
87
|
*/
|
|
88
88
|
static startRelease(voice)
|
|
89
89
|
{
|
|
90
|
-
|
|
90
|
+
ModulationEnvelope.recalculate(voice);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* @param voice {
|
|
94
|
+
* @param voice {Voice} the voice to recalculate
|
|
95
95
|
*/
|
|
96
96
|
static recalculate(voice)
|
|
97
97
|
{
|
|
@@ -100,7 +100,7 @@ export class WorkletModulationEnvelope
|
|
|
100
100
|
// in release? Might need to recalculate the value as it can be modulated
|
|
101
101
|
if (voice.isInRelease)
|
|
102
102
|
{
|
|
103
|
-
env.releaseStartLevel =
|
|
103
|
+
env.releaseStartLevel = ModulationEnvelope.getValue(voice, voice.releaseStartTime, true);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
env.sustainLevel = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
|
|
@@ -130,7 +130,7 @@ export class WorkletModulationEnvelope
|
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Calculates the current modulation envelope value for the given time and voice
|
|
133
|
-
* @param voice {
|
|
133
|
+
* @param voice {Voice} the voice we are working on
|
|
134
134
|
* @param currentTime {number} in seconds
|
|
135
135
|
* @param ignoreRelease {boolean} if true, it will compute the value as if the voice was not released
|
|
136
136
|
* @returns {number} modenv value, from 0 to 1
|
|
@@ -29,7 +29,7 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Pans the voice to the given output buffers
|
|
32
|
-
* @param voice {
|
|
32
|
+
* @param voice {Voice} the voice to pan
|
|
33
33
|
* @param inputBuffer {Float32Array} the input buffer in mono
|
|
34
34
|
* @param outputLeft {Float32Array} left output buffer
|
|
35
35
|
* @param outputRight {Float32Array} right output buffer
|
|
@@ -37,7 +37,7 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
|
|
|
37
37
|
* @param reverbRight {Float32Array} right reverb input
|
|
38
38
|
* @param chorusLeft {Float32Array} left chorus buffer
|
|
39
39
|
* @param chorusRight {Float32Array} right chorus buffer
|
|
40
|
-
* @this {
|
|
40
|
+
* @this {MidiAudioChannel}
|
|
41
41
|
*/
|
|
42
42
|
export function panVoice(voice,
|
|
43
43
|
inputBuffer,
|
|
@@ -65,7 +65,7 @@ export function panVoice(voice,
|
|
|
65
65
|
pan = voice.currentPan;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const gain = this.synth.currentGain;
|
|
68
|
+
const gain = this.synth.currentGain * voice.gain;
|
|
69
69
|
const index = ~~(pan + 500);
|
|
70
70
|
// get voice's gain levels for each channel
|
|
71
71
|
const gainLeft = panTableLeft[index] * gain * this.synth.panLeft;
|
package/synthetizer/audio_engine/{worklet_utilities/worklet_voice.js → engine_components/voice.js}
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* purpose: prepares
|
|
2
|
+
* voice.js
|
|
3
|
+
* purpose: prepares Voices from sample and generator data and manages sample dumping
|
|
4
4
|
* note: sample dumping means sending it over to the AudioWorkletGlobalScope
|
|
5
5
|
*/
|
|
6
6
|
import { MIN_EXCLUSIVE_LENGTH, MIN_NOTE_LENGTH } from "../main_processor.js";
|
|
7
7
|
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
8
8
|
import { WorkletLowpassFilter } from "./lowpass_filter.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { VolumeEnvelope } from "./volume_envelope.js";
|
|
10
|
+
import { ModulationEnvelope } from "./modulation_envelope.js";
|
|
11
11
|
import { addAndClampGenerator, generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
|
|
12
12
|
import { Modulator } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
13
13
|
import { isSystemXG } from "../../../utils/xg_hacks.js";
|
|
@@ -15,7 +15,7 @@ import { isSystemXG } from "../../../utils/xg_hacks.js";
|
|
|
15
15
|
const EXCLUSIVE_CUTOFF_TIME = -2320;
|
|
16
16
|
const EXCLUSIVE_MOD_CUTOFF_TIME = -1130; // less because filter shenanigans
|
|
17
17
|
|
|
18
|
-
class
|
|
18
|
+
class AudioSample
|
|
19
19
|
{
|
|
20
20
|
/**
|
|
21
21
|
* the sample's audio data
|
|
@@ -102,7 +102,7 @@ class WorkletSample
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
105
|
+
* Voice represents a single instance of the
|
|
106
106
|
* SoundFont2 synthesis model.
|
|
107
107
|
* That is:
|
|
108
108
|
* A wavetable oscillator (sample)
|
|
@@ -112,11 +112,11 @@ class WorkletSample
|
|
|
112
112
|
* Modulators (modulators)
|
|
113
113
|
* And MIDI params such as channel, MIDI note, velocity
|
|
114
114
|
*/
|
|
115
|
-
class
|
|
115
|
+
class Voice
|
|
116
116
|
{
|
|
117
117
|
/**
|
|
118
118
|
* The sample of the voice.
|
|
119
|
-
* @type {
|
|
119
|
+
* @type {AudioSample}
|
|
120
120
|
*/
|
|
121
121
|
sample;
|
|
122
122
|
|
|
@@ -126,6 +126,12 @@ class WorkletVoice
|
|
|
126
126
|
*/
|
|
127
127
|
filter;
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Linear gain of the voice. Used with Key Modifiers.
|
|
131
|
+
* @type {number}
|
|
132
|
+
*/
|
|
133
|
+
gain = 1;
|
|
134
|
+
|
|
129
135
|
/**
|
|
130
136
|
* The unmodulated (copied to) generators of the voice.
|
|
131
137
|
* @type {Int16Array}
|
|
@@ -189,13 +195,13 @@ class WorkletVoice
|
|
|
189
195
|
|
|
190
196
|
/**
|
|
191
197
|
* Modulation envelope.
|
|
192
|
-
* @type {
|
|
198
|
+
* @type {ModulationEnvelope}
|
|
193
199
|
*/
|
|
194
|
-
modulationEnvelope = new
|
|
200
|
+
modulationEnvelope = new ModulationEnvelope();
|
|
195
201
|
|
|
196
202
|
/**
|
|
197
203
|
* Volume envelope.
|
|
198
|
-
* @type {
|
|
204
|
+
* @type {VolumeEnvelope}
|
|
199
205
|
*/
|
|
200
206
|
volumeEnvelope;
|
|
201
207
|
|
|
@@ -262,9 +268,9 @@ class WorkletVoice
|
|
|
262
268
|
exclusiveClass = 0;
|
|
263
269
|
|
|
264
270
|
/**
|
|
265
|
-
* Creates a
|
|
271
|
+
* Creates a Voice
|
|
266
272
|
* @param sampleRate {number}
|
|
267
|
-
* @param workletSample {
|
|
273
|
+
* @param workletSample {AudioSample}
|
|
268
274
|
* @param midiNote {number}
|
|
269
275
|
* @param velocity {number}
|
|
270
276
|
* @param channel {number}
|
|
@@ -300,19 +306,19 @@ class WorkletVoice
|
|
|
300
306
|
this.startTime = currentTime;
|
|
301
307
|
this.targetKey = targetKey;
|
|
302
308
|
this.realKey = realKey;
|
|
303
|
-
this.volumeEnvelope = new
|
|
309
|
+
this.volumeEnvelope = new VolumeEnvelope(sampleRate, generators[generatorTypes.sustainVolEnv]);
|
|
304
310
|
}
|
|
305
311
|
|
|
306
312
|
/**
|
|
307
313
|
* copies the voice
|
|
308
|
-
* @param voice {
|
|
314
|
+
* @param voice {Voice}
|
|
309
315
|
* @param currentTime {number}
|
|
310
|
-
* @returns
|
|
316
|
+
* @returns Voice
|
|
311
317
|
*/
|
|
312
318
|
static copy(voice, currentTime)
|
|
313
319
|
{
|
|
314
320
|
const sampleToCopy = voice.sample;
|
|
315
|
-
const sample = new
|
|
321
|
+
const sample = new AudioSample(
|
|
316
322
|
sampleToCopy.sampleData,
|
|
317
323
|
sampleToCopy.playbackStep,
|
|
318
324
|
sampleToCopy.cursor,
|
|
@@ -322,7 +328,7 @@ class WorkletVoice
|
|
|
322
328
|
sampleToCopy.end,
|
|
323
329
|
sampleToCopy.loopingMode
|
|
324
330
|
);
|
|
325
|
-
return new
|
|
331
|
+
return new Voice(
|
|
326
332
|
voice.volumeEnvelope.sampleRate,
|
|
327
333
|
sample,
|
|
328
334
|
voice.midiNote,
|
|
@@ -345,8 +351,8 @@ class WorkletVoice
|
|
|
345
351
|
this.release(currentTime, MIN_EXCLUSIVE_LENGTH);
|
|
346
352
|
this.modulatedGenerators[generatorTypes.releaseVolEnv] = EXCLUSIVE_CUTOFF_TIME; // make the release nearly instant
|
|
347
353
|
this.modulatedGenerators[generatorTypes.releaseModEnv] = EXCLUSIVE_MOD_CUTOFF_TIME;
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
VolumeEnvelope.recalculate(this);
|
|
355
|
+
ModulationEnvelope.recalculate(this);
|
|
350
356
|
}
|
|
351
357
|
|
|
352
358
|
/**
|
|
@@ -371,17 +377,17 @@ class WorkletVoice
|
|
|
371
377
|
* @param velocity {number} the velocity to use
|
|
372
378
|
* @param realKey {number} the real MIDI note if the "midiNote" was changed by MIDI Tuning Standard
|
|
373
379
|
* @this {SpessaSynthProcessor}
|
|
374
|
-
* @returns {
|
|
380
|
+
* @returns {Voice[]} output is an array of Voices
|
|
375
381
|
*/
|
|
376
|
-
export function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
export function getVoices(channel,
|
|
383
|
+
midiNote,
|
|
384
|
+
velocity,
|
|
385
|
+
realKey)
|
|
380
386
|
{
|
|
381
387
|
/**
|
|
382
|
-
* @type {
|
|
388
|
+
* @type {Voice[]}
|
|
383
389
|
*/
|
|
384
|
-
let
|
|
390
|
+
let voices;
|
|
385
391
|
const channelObject = this.midiAudioChannels[channel];
|
|
386
392
|
|
|
387
393
|
// override patch
|
|
@@ -401,7 +407,7 @@ export function getWorkletVoices(channel,
|
|
|
401
407
|
// if cached, return it!
|
|
402
408
|
if (cached !== undefined)
|
|
403
409
|
{
|
|
404
|
-
return cached.map(v =>
|
|
410
|
+
return cached.map(v => Voice.copy(v, this.currentSynthTime));
|
|
405
411
|
}
|
|
406
412
|
|
|
407
413
|
// not cached...
|
|
@@ -411,9 +417,9 @@ export function getWorkletVoices(channel,
|
|
|
411
417
|
preset = this.soundfontManager.getPreset(bank, program, isSystemXG(this.system));
|
|
412
418
|
}
|
|
413
419
|
/**
|
|
414
|
-
* @returns {
|
|
420
|
+
* @returns {Voice[]}
|
|
415
421
|
*/
|
|
416
|
-
|
|
422
|
+
voices = preset.getSamplesAndGenerators(midiNote, velocity)
|
|
417
423
|
.reduce((voices, sampleAndGenerators) =>
|
|
418
424
|
{
|
|
419
425
|
if (sampleAndGenerators.sample.getAudioData() === undefined)
|
|
@@ -457,9 +463,9 @@ export function getWorkletVoices(channel,
|
|
|
457
463
|
/**
|
|
458
464
|
* create the worklet sample
|
|
459
465
|
* offsets are calculated at note on time (to allow for modulation of them)
|
|
460
|
-
* @type {
|
|
466
|
+
* @type {AudioSample}
|
|
461
467
|
*/
|
|
462
|
-
const workletSample = new
|
|
468
|
+
const workletSample = new AudioSample(
|
|
463
469
|
sampleAndGenerators.sample.sampleData,
|
|
464
470
|
(sampleAndGenerators.sample.sampleRate / this.sampleRate) * Math.pow(
|
|
465
471
|
2,
|
|
@@ -486,12 +492,12 @@ export function getWorkletVoices(channel,
|
|
|
486
492
|
// Velocity: velocity,
|
|
487
493
|
// TargetKey: targetKey,
|
|
488
494
|
// MidiNote: midiNote,
|
|
489
|
-
//
|
|
495
|
+
// AudioSample: workletSample
|
|
490
496
|
// }]);
|
|
491
497
|
|
|
492
498
|
|
|
493
499
|
voices.push(
|
|
494
|
-
new
|
|
500
|
+
new Voice(
|
|
495
501
|
this.sampleRate,
|
|
496
502
|
workletSample,
|
|
497
503
|
midiNote,
|
|
@@ -507,7 +513,7 @@ export function getWorkletVoices(channel,
|
|
|
507
513
|
return voices;
|
|
508
514
|
}, []);
|
|
509
515
|
// cache the voice
|
|
510
|
-
this.setCachedVoice(bank, program, midiNote, velocity,
|
|
511
|
-
|
|
512
|
-
return
|
|
516
|
+
this.setCachedVoice(bank, program, midiNote, velocity, voices.map(v =>
|
|
517
|
+
Voice.copy(v, this.currentSynthTime)));
|
|
518
|
+
return voices;
|
|
513
519
|
}
|
|
@@ -21,10 +21,10 @@ const PERCEIVED_GAIN_SILENCE = 0.000015; // can't go lower than that (see #50)
|
|
|
21
21
|
* 2 - hold/peak
|
|
22
22
|
* 3 - decay
|
|
23
23
|
* 4 - sustain
|
|
24
|
-
* release
|
|
24
|
+
* release indicates by isInRelease property
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
export class
|
|
27
|
+
export class VolumeEnvelope
|
|
28
28
|
{
|
|
29
29
|
/**
|
|
30
30
|
* The envelope's current time in samples
|
|
@@ -97,22 +97,22 @@ export class WorkletVolumeEnvelope
|
|
|
97
97
|
*/
|
|
98
98
|
sustainDbRelative = 0;
|
|
99
99
|
/**
|
|
100
|
-
* The time in samples to the end of delay stage, relative to start of the envelope
|
|
100
|
+
* The time in samples to the end of delay stage, relative to the start of the envelope
|
|
101
101
|
* @type {number}
|
|
102
102
|
*/
|
|
103
103
|
delayEnd = 0;
|
|
104
104
|
/**
|
|
105
|
-
* The time in samples to the end of attack stage, relative to start of the envelope
|
|
105
|
+
* The time in samples to the end of attack stage, relative to the start of the envelope
|
|
106
106
|
* @type {number}
|
|
107
107
|
*/
|
|
108
108
|
attackEnd = 0;
|
|
109
109
|
/**
|
|
110
|
-
* The time in samples to the end of hold stage, relative to start of the envelope
|
|
110
|
+
* The time in samples to the end of hold stage, relative to the start of the envelope
|
|
111
111
|
* @type {number}
|
|
112
112
|
*/
|
|
113
113
|
holdEnd = 0;
|
|
114
114
|
/**
|
|
115
|
-
* The time in samples to the end of decay stage, relative to start of the envelope
|
|
115
|
+
* The time in samples to the end of decay stage, relative to the start of the envelope
|
|
116
116
|
* @type {number}
|
|
117
117
|
*/
|
|
118
118
|
decayEnd = 0;
|
|
@@ -127,7 +127,7 @@ export class WorkletVolumeEnvelope
|
|
|
127
127
|
/**
|
|
128
128
|
* if sustain stge is silent,
|
|
129
129
|
* then we can turn off the voice when it is silent.
|
|
130
|
-
* We can't do that with modulated as it can silence the volume and then raise it again and the voice must keep playing
|
|
130
|
+
* We can't do that with modulated as it can silence the volume and then raise it again, and the voice must keep playing
|
|
131
131
|
* @type {boolean}
|
|
132
132
|
*/
|
|
133
133
|
this.canEndOnSilentSustain = initialDecay / 10 >= PERCEIVED_DB_SILENCE;
|
|
@@ -135,18 +135,18 @@ export class WorkletVolumeEnvelope
|
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
137
|
* Starts the release phase in the envelope
|
|
138
|
-
* @param voice {
|
|
138
|
+
* @param voice {Voice} the voice this envelope belongs to
|
|
139
139
|
*/
|
|
140
140
|
static startRelease(voice)
|
|
141
141
|
{
|
|
142
142
|
voice.volumeEnvelope.releaseStartTimeSamples = voice.volumeEnvelope.currentSampleTime;
|
|
143
143
|
voice.volumeEnvelope.currentReleaseGain = decibelAttenuationToGain(voice.volumeEnvelope.currentAttenuationDb);
|
|
144
|
-
|
|
144
|
+
VolumeEnvelope.recalculate(voice);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
148
|
* Recalculates the envelope
|
|
149
|
-
* @param voice {
|
|
149
|
+
* @param voice {Voice} the voice this envelope belongs to
|
|
150
150
|
*/
|
|
151
151
|
static recalculate(voice)
|
|
152
152
|
{
|
|
@@ -167,8 +167,8 @@ export class WorkletVolumeEnvelope
|
|
|
167
167
|
// calculate durations
|
|
168
168
|
env.attackDuration = timecentsToSamples(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
|
|
169
169
|
|
|
170
|
-
// decay: sfspec page 35: the time is for change from attenuation to -100dB
|
|
171
|
-
// therefore we need to calculate the real time
|
|
170
|
+
// decay: sfspec page 35: the time is for change from attenuation to -100dB,
|
|
171
|
+
// therefore, we need to calculate the real time
|
|
172
172
|
// (changing from attenuation to sustain instead of -100dB)
|
|
173
173
|
const fullChange = voice.modulatedGenerators[generatorTypes.decayVolEnv];
|
|
174
174
|
const keyNumAddition = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay];
|
|
@@ -181,7 +181,7 @@ export class WorkletVolumeEnvelope
|
|
|
181
181
|
env.delayEnd = timecentsToSamples(voice.modulatedGenerators[generatorTypes.delayVolEnv]);
|
|
182
182
|
env.attackEnd = env.attackDuration + env.delayEnd;
|
|
183
183
|
|
|
184
|
-
// make sure to take keyNumToVolEnvHold into account
|
|
184
|
+
// make sure to take keyNumToVolEnvHold into account!
|
|
185
185
|
const holdExcursion = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvHold];
|
|
186
186
|
env.holdEnd = timecentsToSamples(voice.modulatedGenerators[generatorTypes.holdVolEnv]
|
|
187
187
|
+ holdExcursion)
|
|
@@ -217,7 +217,7 @@ export class WorkletVolumeEnvelope
|
|
|
217
217
|
// (to make volume go down exponentially)
|
|
218
218
|
// attack is linear (in gain) so we need to do get db from that
|
|
219
219
|
let elapsed = 1 - ((env.attackEnd - env.releaseStartTimeSamples) / env.attackDuration);
|
|
220
|
-
// calculate the gain that the attack would have
|
|
220
|
+
// calculate the gain that the attack would have, so
|
|
221
221
|
// turn that into db
|
|
222
222
|
env.releaseStartDb = 20 * Math.log10(elapsed) * -1;
|
|
223
223
|
break;
|
|
@@ -241,8 +241,8 @@ export class WorkletVolumeEnvelope
|
|
|
241
241
|
}
|
|
242
242
|
env.currentReleaseGain = decibelAttenuationToGain(env.releaseStartDb);
|
|
243
243
|
|
|
244
|
-
// release: sfspec page 35: the time is for change from attenuation to -100dB
|
|
245
|
-
// therefore we need to calculate the real time
|
|
244
|
+
// release: sfspec page 35: the time is for change from attenuation to -100dB,
|
|
245
|
+
// therefore, we need to calculate the real time
|
|
246
246
|
// (changing from release start to -100dB instead of from peak to -100dB)
|
|
247
247
|
const releaseFraction = (DB_SILENCE - env.releaseStartDb) / DB_SILENCE;
|
|
248
248
|
env.releaseDuration *= releaseFraction;
|
|
@@ -252,7 +252,7 @@ export class WorkletVolumeEnvelope
|
|
|
252
252
|
|
|
253
253
|
/**
|
|
254
254
|
* Applies volume envelope gain to the given output buffer
|
|
255
|
-
* @param voice {
|
|
255
|
+
* @param voice {Voice} the voice we're working on
|
|
256
256
|
* @param audioBuffer {Float32Array} the audio buffer to modify
|
|
257
257
|
* @param centibelOffset {number} the centibel offset of volume, for modLFOtoVolume
|
|
258
258
|
* @param smoothingFactor {number} the adjusted smoothing factor for the envelope
|
package/synthetizer/audio_engine/{worklet_utilities → engine_components}/wavetable_oscillator.js
RENAMED
|
@@ -19,7 +19,7 @@ export class WavetableOscillator
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Fills the output buffer with raw sample data using linear interpolation
|
|
22
|
-
* @param voice {
|
|
22
|
+
* @param voice {Voice} the voice we're working on
|
|
23
23
|
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
24
24
|
*/
|
|
25
25
|
static getSampleLinear(voice, outputBuffer)
|
|
@@ -93,7 +93,7 @@ export class WavetableOscillator
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Fills the output buffer with raw sample data using no interpolation (nearest neighbor)
|
|
96
|
-
* @param voice {
|
|
96
|
+
* @param voice {Voice} the voice we're working on
|
|
97
97
|
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
98
98
|
*/
|
|
99
99
|
static getSampleNearest(voice, outputBuffer)
|
|
@@ -154,7 +154,7 @@ export class WavetableOscillator
|
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
156
|
* Fills the output buffer with raw sample data using cubic interpolation
|
|
157
|
-
* @param voice {
|
|
157
|
+
* @param voice {Voice} the voice we're working on
|
|
158
158
|
* @param outputBuffer {Float32Array} the output buffer to write to
|
|
159
159
|
*/
|
|
160
160
|
static getSampleCubic(voice, outputBuffer)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { midiControllers } from "../../../../
|
|
2
|
-
import { computeModulators } from "../../
|
|
3
|
-
import { channelConfiguration, dataEntryStates } from "../../
|
|
1
|
+
import { midiControllers } from "../../../../midi/midi_message.js";
|
|
2
|
+
import { computeModulators } from "../../engine_components/compute_modulator.js";
|
|
3
|
+
import { channelConfiguration, dataEntryStates } from "../../engine_components/controller_tables.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @param controllerNumber {number}
|
|
7
7
|
* @param controllerValue {number}
|
|
8
8
|
* @param force {boolean}
|
|
9
|
-
* @this {
|
|
9
|
+
* @this {MidiAudioChannel}
|
|
10
10
|
*/
|
|
11
11
|
export function controllerChange(controllerNumber, controllerValue, force = false)
|
|
12
12
|
{
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SYNTHESIZER_GAIN } from "../../main_processor.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @enum {number}
|
|
5
|
+
*/
|
|
6
|
+
export const masterParameterType = {
|
|
7
|
+
mainVolume: 0,
|
|
8
|
+
masterPan: 1,
|
|
9
|
+
voicesCap: 2,
|
|
10
|
+
interpolationType: 3,
|
|
11
|
+
midiSystem: 4
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @this {SpessaSynthProcessor}
|
|
16
|
+
* @param type {masterParameterType}
|
|
17
|
+
* @param value {number|string|interpolationTypes}
|
|
18
|
+
*/
|
|
19
|
+
export function setMasterParameter(type, value)
|
|
20
|
+
{
|
|
21
|
+
switch (type)
|
|
22
|
+
{
|
|
23
|
+
case masterParameterType.masterPan:
|
|
24
|
+
let pan = value;
|
|
25
|
+
this.pan = pan;
|
|
26
|
+
// clamp to 0-1 (0 is left)
|
|
27
|
+
pan = (pan / 2) + 0.5;
|
|
28
|
+
this.panLeft = (1 - pan);
|
|
29
|
+
this.panRight = (pan);
|
|
30
|
+
break;
|
|
31
|
+
|
|
32
|
+
case masterParameterType.mainVolume:
|
|
33
|
+
this.masterGain = value * SYNTHESIZER_GAIN;
|
|
34
|
+
this.setMasterParameter(masterParameterType.masterPan, this.pan);
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case masterParameterType.voicesCap:
|
|
38
|
+
this.voiceCap = value;
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case masterParameterType.interpolationType:
|
|
42
|
+
this.interpolationType = value;
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
case masterParameterType.midiSystem:
|
|
46
|
+
this.setSystem(value);
|
|
47
|
+
}
|
|
48
|
+
}
|