spessasynth_lib 0.0.1
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/.idea/modules.xml +8 -0
- package/.idea/spessasynth_lib.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/copy_version.sh +38 -0
- package/index.js +73 -0
- package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
- package/package/@types/index.d.ts +34 -0
- package/package/@types/midi_handler/midi_handler.d.ts +39 -0
- package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
- package/package/@types/midi_parser/midi_data.d.ts +95 -0
- package/package/@types/midi_parser/midi_editor.d.ts +45 -0
- package/package/@types/midi_parser/midi_loader.d.ts +100 -0
- package/package/@types/midi_parser/midi_message.d.ts +154 -0
- package/package/@types/midi_parser/midi_writer.d.ts +6 -0
- package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
- package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
- package/package/@types/sequencer/sequencer.d.ts +180 -0
- package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
- package/package/@types/soundfont/read/generators.d.ts +98 -0
- package/package/@types/soundfont/read/instruments.d.ts +50 -0
- package/package/@types/soundfont/read/modulators.d.ts +73 -0
- package/package/@types/soundfont/read/presets.d.ts +87 -0
- package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
- package/package/@types/soundfont/read/samples.d.ts +134 -0
- package/package/@types/soundfont/read/zones.d.ts +141 -0
- package/package/@types/soundfont/soundfont.d.ts +76 -0
- package/package/@types/soundfont/write/ibag.d.ts +6 -0
- package/package/@types/soundfont/write/igen.d.ts +6 -0
- package/package/@types/soundfont/write/imod.d.ts +6 -0
- package/package/@types/soundfont/write/inst.d.ts +6 -0
- package/package/@types/soundfont/write/pbag.d.ts +6 -0
- package/package/@types/soundfont/write/pgen.d.ts +6 -0
- package/package/@types/soundfont/write/phdr.d.ts +6 -0
- package/package/@types/soundfont/write/pmod.d.ts +6 -0
- package/package/@types/soundfont/write/sdta.d.ts +11 -0
- package/package/@types/soundfont/write/shdr.d.ts +8 -0
- package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
- package/package/@types/soundfont/write/write.d.ts +21 -0
- package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
- package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
- package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
- package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
- package/package/@types/synthetizer/synthetizer.d.ts +294 -0
- package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
- package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
- package/package/@types/synthetizer/worklet_url.d.ts +5 -0
- package/package/@types/utils/buffer_to_wav.d.ts +8 -0
- package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
- package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
- package/package/@types/utils/byte_functions/string.d.ts +22 -0
- package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
- package/package/@types/utils/indexed_array.d.ts +21 -0
- package/package/@types/utils/loggin.d.ts +26 -0
- package/package/@types/utils/other.d.ts +32 -0
- package/package/LICENSE +26 -0
- package/package/README.md +84 -0
- package/package/externals/NOTICE +9 -0
- package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
- package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
- package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/package/externals/stbvorbis_sync/LICENSE +202 -0
- package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/package/index.js +73 -0
- package/package/midi_handler/README.md +3 -0
- package/package/midi_handler/midi_handler.js +118 -0
- package/package/midi_handler/web_midi_link.js +41 -0
- package/package/midi_parser/README.md +3 -0
- package/package/midi_parser/midi_data.js +121 -0
- package/package/midi_parser/midi_editor.js +557 -0
- package/package/midi_parser/midi_loader.js +502 -0
- package/package/midi_parser/midi_message.js +234 -0
- package/package/midi_parser/midi_writer.js +95 -0
- package/package/midi_parser/rmidi_writer.js +271 -0
- package/package/midi_parser/used_keys_loaded.js +172 -0
- package/package/package.json +43 -0
- package/package/sequencer/README.md +23 -0
- package/package/sequencer/sequencer.js +439 -0
- package/package/sequencer/worklet_sequencer/events.js +92 -0
- package/package/sequencer/worklet_sequencer/play.js +309 -0
- package/package/sequencer/worklet_sequencer/process_event.js +167 -0
- package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
- package/package/sequencer/worklet_sequencer/song_control.js +193 -0
- package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
- package/package/soundfont/README.md +8 -0
- package/package/soundfont/read/generators.js +212 -0
- package/package/soundfont/read/instruments.js +125 -0
- package/package/soundfont/read/modulators.js +249 -0
- package/package/soundfont/read/presets.js +300 -0
- package/package/soundfont/read/riff_chunk.js +81 -0
- package/package/soundfont/read/samples.js +398 -0
- package/package/soundfont/read/zones.js +310 -0
- package/package/soundfont/soundfont.js +357 -0
- package/package/soundfont/write/ibag.js +39 -0
- package/package/soundfont/write/igen.js +75 -0
- package/package/soundfont/write/imod.js +46 -0
- package/package/soundfont/write/inst.js +34 -0
- package/package/soundfont/write/pbag.js +39 -0
- package/package/soundfont/write/pgen.js +77 -0
- package/package/soundfont/write/phdr.js +42 -0
- package/package/soundfont/write/pmod.js +46 -0
- package/package/soundfont/write/sdta.js +72 -0
- package/package/soundfont/write/shdr.js +54 -0
- package/package/soundfont/write/soundfont_trimmer.js +169 -0
- package/package/soundfont/write/write.js +180 -0
- package/package/synthetizer/README.md +6 -0
- package/package/synthetizer/audio_effects/effects_config.js +21 -0
- package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
- package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/package/synthetizer/audio_effects/reverb.js +24 -0
- package/package/synthetizer/synth_event_handler.js +156 -0
- package/package/synthetizer/synthetizer.js +766 -0
- package/package/synthetizer/worklet_processor.min.js +13 -0
- package/package/synthetizer/worklet_system/README.md +6 -0
- package/package/synthetizer/worklet_system/main_processor.js +363 -0
- package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
- package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
- package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
- package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
- package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
- package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
- package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
- package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
- package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
- package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
- package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
- package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
- package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
- package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
- package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
- package/package/synthetizer/worklet_url.js +5 -0
- package/package/utils/README.md +4 -0
- package/package/utils/buffer_to_wav.js +101 -0
- package/package/utils/byte_functions/big_endian.js +28 -0
- package/package/utils/byte_functions/little_endian.js +74 -0
- package/package/utils/byte_functions/string.js +97 -0
- package/package/utils/byte_functions/variable_length_quantity.js +37 -0
- package/package/utils/encode_vorbis.js +30 -0
- package/package/utils/indexed_array.js +41 -0
- package/package/utils/loggin.js +79 -0
- package/package/utils/other.js +54 -0
- package/package.json +43 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { generatorTypes } from '../../../soundfont/read/generators.js'
|
|
2
|
+
import { consoleColors } from '../../../utils/other.js'
|
|
3
|
+
import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Release a note
|
|
7
|
+
* @param channel {number}
|
|
8
|
+
* @param midiNote {number}
|
|
9
|
+
* @this {SpessaSynthProcessor}
|
|
10
|
+
*/
|
|
11
|
+
export function noteOff(channel, midiNote)
|
|
12
|
+
{
|
|
13
|
+
if(midiNote > 127 || midiNote < 0)
|
|
14
|
+
{
|
|
15
|
+
SpessaSynthWarn(`Received a noteOn for note`, midiNote, "Ignoring.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
midiNote += this.workletProcessorChannels[channel].channelTransposeKeyShift;
|
|
19
|
+
|
|
20
|
+
// if high performance mode, kill notes instead of stopping them
|
|
21
|
+
if(this.highPerformanceMode)
|
|
22
|
+
{
|
|
23
|
+
// if the channel is percussion channel, do not kill the notes
|
|
24
|
+
if(!this.workletProcessorChannels[channel].drumChannel)
|
|
25
|
+
{
|
|
26
|
+
this.killNote(channel, midiNote);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const channelVoices = this.workletProcessorChannels[channel].voices;
|
|
32
|
+
channelVoices.forEach(v => {
|
|
33
|
+
if(v.midiNote !== midiNote || v.isInRelease === true)
|
|
34
|
+
{
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// if hold pedal, move to sustain
|
|
38
|
+
if(this.workletProcessorChannels[channel].holdPedal) {
|
|
39
|
+
this.workletProcessorChannels[channel].sustainedVoices.push(v);
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
{
|
|
43
|
+
this.releaseVoice(v);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
this.callEvent("noteoff", {
|
|
47
|
+
midiNote: midiNote - this.workletProcessorChannels[channel].channelTransposeKeyShift,
|
|
48
|
+
channel: channel
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Stops a note nearly instantly
|
|
54
|
+
* @param channel {number}
|
|
55
|
+
* @param midiNote {number}
|
|
56
|
+
* @this {SpessaSynthProcessor}
|
|
57
|
+
*/
|
|
58
|
+
export function killNote(channel, midiNote)
|
|
59
|
+
{
|
|
60
|
+
this.workletProcessorChannels[channel].voices.forEach(v => {
|
|
61
|
+
if(v.midiNote !== midiNote)
|
|
62
|
+
{
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
v.modulatedGenerators[generatorTypes.releaseVolEnv] = -12000; // set release to be very short
|
|
66
|
+
this.releaseVoice(v);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* stops all notes
|
|
72
|
+
* @param channel {number}
|
|
73
|
+
* @param force {boolean}
|
|
74
|
+
* @this {SpessaSynthProcessor}
|
|
75
|
+
*/
|
|
76
|
+
export function stopAll(channel, force = false)
|
|
77
|
+
{
|
|
78
|
+
const channelVoices = this.workletProcessorChannels[channel].voices;
|
|
79
|
+
if(force)
|
|
80
|
+
{
|
|
81
|
+
// force stop all
|
|
82
|
+
channelVoices.length = 0;
|
|
83
|
+
this.workletProcessorChannels[channel].sustainedVoices.length = 0;
|
|
84
|
+
this.sendChannelProperties();
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
{
|
|
88
|
+
channelVoices.forEach(v => {
|
|
89
|
+
if(v.isInRelease) return;
|
|
90
|
+
this.releaseVoice(v);
|
|
91
|
+
});
|
|
92
|
+
this.workletProcessorChannels[channel].sustainedVoices.forEach(v => {
|
|
93
|
+
this.releaseVoice(v);
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @this {SpessaSynthProcessor}
|
|
100
|
+
* @param force {boolean}
|
|
101
|
+
*/
|
|
102
|
+
export function stopAllChannels(force = false)
|
|
103
|
+
{
|
|
104
|
+
SpessaSynthInfo("%cStop all received!", consoleColors.info);
|
|
105
|
+
for (let i = 0; i < this.workletProcessorChannels.length; i++) {
|
|
106
|
+
this.stopAll(i, force);
|
|
107
|
+
}
|
|
108
|
+
this.callEvent("stopall", undefined);
|
|
109
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getWorkletVoices } from '../worklet_utilities/worklet_voice.js'
|
|
2
|
+
import { generatorTypes } from '../../../soundfont/read/generators.js'
|
|
3
|
+
import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
|
|
4
|
+
import { recalculateVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Append the voices
|
|
8
|
+
* @param channel {number}
|
|
9
|
+
* @param midiNote {number}
|
|
10
|
+
* @param velocity {number}
|
|
11
|
+
* @param enableDebugging {boolean}
|
|
12
|
+
* @param sendEvent {boolean}
|
|
13
|
+
* @param startTime {number}
|
|
14
|
+
* @this {SpessaSynthProcessor}
|
|
15
|
+
*/
|
|
16
|
+
export function noteOn(channel, midiNote, velocity, enableDebugging = false, sendEvent = true, startTime = currentTime)
|
|
17
|
+
{
|
|
18
|
+
if (velocity === 0)
|
|
19
|
+
{
|
|
20
|
+
this.noteOff(channel, midiNote);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
(this.highPerformanceMode && this.totalVoicesAmount > 200 && velocity < 40) ||
|
|
26
|
+
(this.highPerformanceMode && velocity < 10) ||
|
|
27
|
+
(this.workletProcessorChannels[channel].isMuted)
|
|
28
|
+
)
|
|
29
|
+
{
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
midiNote += this.workletProcessorChannels[channel].channelTransposeKeyShift;
|
|
34
|
+
|
|
35
|
+
if(midiNote > 127 || midiNote < 0)
|
|
36
|
+
{
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// get voices
|
|
41
|
+
const voices = getWorkletVoices(
|
|
42
|
+
channel,
|
|
43
|
+
midiNote,
|
|
44
|
+
velocity,
|
|
45
|
+
this.workletProcessorChannels[channel].preset,
|
|
46
|
+
startTime,
|
|
47
|
+
sampleRate,
|
|
48
|
+
data => this.sampleDump(data.channel, data.sampleID, data.sampleData),
|
|
49
|
+
this.workletProcessorChannels[channel].cachedVoices,
|
|
50
|
+
enableDebugging
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// add voices and exclusive class apply
|
|
54
|
+
const channelVoices = this.workletProcessorChannels[channel].voices;
|
|
55
|
+
voices.forEach(voice => {
|
|
56
|
+
const exclusive = voice.generators[generatorTypes.exclusiveClass];
|
|
57
|
+
if(exclusive !== 0)
|
|
58
|
+
{
|
|
59
|
+
channelVoices.forEach(v => {
|
|
60
|
+
if(v.generators[generatorTypes.exclusiveClass] === exclusive)
|
|
61
|
+
{
|
|
62
|
+
this.releaseVoice(v);
|
|
63
|
+
v.modulatedGenerators[generatorTypes.releaseVolEnv] = -7000; // make the release nearly instant
|
|
64
|
+
v.modulatedGenerators[generatorTypes.releaseModEnv] = -7000;
|
|
65
|
+
recalculateVolumeEnvelope(v);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
// compute all modulators
|
|
70
|
+
computeModulators(voice, this.workletProcessorChannels[channel].midiControllers);
|
|
71
|
+
// set initial pan to avoid split second changing from middle to the correct value
|
|
72
|
+
voice.currentPan = ((Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan] )) + 500) / 1000) // 0 to 1
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.totalVoicesAmount += voices.length;
|
|
76
|
+
// cap the voices
|
|
77
|
+
if(this.totalVoicesAmount > this.voiceCap)
|
|
78
|
+
{
|
|
79
|
+
this.voiceKilling(voices.length);
|
|
80
|
+
}
|
|
81
|
+
channelVoices.push(...voices);
|
|
82
|
+
if(sendEvent)
|
|
83
|
+
{
|
|
84
|
+
this.sendChannelProperties();
|
|
85
|
+
this.callEvent("noteon", {
|
|
86
|
+
midiNote: midiNote - this.workletProcessorChannels[channel].channelTransposeKeyShift,
|
|
87
|
+
channel: channel,
|
|
88
|
+
velocity: velocity,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { midiControllers } from '../../../midi_parser/midi_message.js'
|
|
2
|
+
import { SoundFont2 } from '../../../soundfont/soundfont.js'
|
|
3
|
+
import { clearSamplesList } from '../worklet_utilities/worklet_voice.js'
|
|
4
|
+
import { generatorTypes } from '../../../soundfont/read/generators.js'
|
|
5
|
+
import { returnMessageType } from '../message_protocol/worklet_message.js'
|
|
6
|
+
import { SpessaSynthInfo } from '../../../utils/loggin.js'
|
|
7
|
+
import { consoleColors } from '../../../utils/other.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* executes a program change
|
|
11
|
+
* @param channel {number}
|
|
12
|
+
* @param programNumber {number}
|
|
13
|
+
* @param userChange {boolean}
|
|
14
|
+
* @this {SpessaSynthProcessor}
|
|
15
|
+
*/
|
|
16
|
+
export function programChange(channel, programNumber, userChange=false)
|
|
17
|
+
{
|
|
18
|
+
/**
|
|
19
|
+
* @type {WorkletProcessorChannel}
|
|
20
|
+
*/
|
|
21
|
+
const channelObject = this.workletProcessorChannels[channel];
|
|
22
|
+
if(channelObject.lockPreset)
|
|
23
|
+
{
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// always 128 for percussion
|
|
27
|
+
const bank = channelObject.drumChannel ? 128 : channelObject.midiControllers[midiControllers.bankSelect];
|
|
28
|
+
const preset = this.soundfont.getPreset(bank, programNumber);
|
|
29
|
+
this.setPreset(channel, preset);
|
|
30
|
+
this.callEvent("programchange",{
|
|
31
|
+
channel: channel,
|
|
32
|
+
program: preset.program,
|
|
33
|
+
bank: preset.bank,
|
|
34
|
+
userCalled: userChange
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param channel {number}
|
|
40
|
+
* @param preset {Preset}
|
|
41
|
+
* @this {SpessaSynthProcessor}
|
|
42
|
+
*/
|
|
43
|
+
export function setPreset(channel, preset)
|
|
44
|
+
{
|
|
45
|
+
if(this.workletProcessorChannels[channel].lockPreset)
|
|
46
|
+
{
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.workletProcessorChannels[channel].preset = preset;
|
|
50
|
+
|
|
51
|
+
// reset cached voices
|
|
52
|
+
this.workletProcessorChannels[channel].cachedVoices = [];
|
|
53
|
+
for (let i = 0; i < 128; i++) {
|
|
54
|
+
this.workletProcessorChannels[channel].cachedVoices.push([]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Toggles drums on a given channel
|
|
60
|
+
* @param channel {number}
|
|
61
|
+
* @param isDrum {boolean}
|
|
62
|
+
* @this {SpessaSynthProcessor}
|
|
63
|
+
*/
|
|
64
|
+
export function setDrums(channel, isDrum)
|
|
65
|
+
{
|
|
66
|
+
const channelObject = this.workletProcessorChannels[channel];
|
|
67
|
+
if(channelObject.lockPreset)
|
|
68
|
+
{
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if(isDrum)
|
|
72
|
+
{
|
|
73
|
+
// clear transpose
|
|
74
|
+
channelObject.channelTransposeKeyShift = 0;
|
|
75
|
+
channelObject.drumChannel = true;
|
|
76
|
+
this.setPreset(channel, this.soundfont.getPreset(128, channelObject.preset.program));
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
{
|
|
80
|
+
channelObject.drumChannel = false;
|
|
81
|
+
this.setPreset(channel, this.soundfont.getPreset(channelObject.midiControllers[midiControllers.bankSelect], channelObject.preset.program));
|
|
82
|
+
}
|
|
83
|
+
this.callEvent("drumchange",{
|
|
84
|
+
channel: channel,
|
|
85
|
+
isDrumChannel: channelObject.drumChannel
|
|
86
|
+
});
|
|
87
|
+
this.sendChannelProperties();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @this {SpessaSynthProcessor}
|
|
92
|
+
*/
|
|
93
|
+
export function sendPresetList()
|
|
94
|
+
{
|
|
95
|
+
this.callEvent("presetlistchange", this.soundfont.presets.map(p => {
|
|
96
|
+
return {presetName: p.presetName, bank: p.bank, program: p.program};
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param buffer {ArrayBuffer}
|
|
102
|
+
* @this {SpessaSynthProcessor}
|
|
103
|
+
*/
|
|
104
|
+
export function reloadSoundFont(buffer)
|
|
105
|
+
{
|
|
106
|
+
this.stopAllChannels(true);
|
|
107
|
+
delete this.soundfont;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
this.soundfont = new SoundFont2(buffer);
|
|
112
|
+
}
|
|
113
|
+
catch (e)
|
|
114
|
+
{
|
|
115
|
+
this.post({
|
|
116
|
+
messageType: returnMessageType.soundfontError,
|
|
117
|
+
messageData: e
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
clearSamplesList();
|
|
122
|
+
delete this.workletDumpedSamplesList;
|
|
123
|
+
this.workletDumpedSamplesList = [];
|
|
124
|
+
this.defaultPreset = this.soundfont.getPreset(0, 0);
|
|
125
|
+
this.drumPreset = this.soundfont.getPreset(128, 0);
|
|
126
|
+
|
|
127
|
+
for(let i = 0; i < this.workletProcessorChannels.length; i++)
|
|
128
|
+
{
|
|
129
|
+
const channelObject = this.workletProcessorChannels[i];
|
|
130
|
+
channelObject.cachedVoices = [];
|
|
131
|
+
for (let j = 0; j < 128; j++) {
|
|
132
|
+
channelObject.cachedVoices.push([]);
|
|
133
|
+
}
|
|
134
|
+
channelObject.lockPreset = false;
|
|
135
|
+
this.programChange(i, channelObject.preset.program);
|
|
136
|
+
}
|
|
137
|
+
this.post({messageType: returnMessageType.ready, messageData: undefined});
|
|
138
|
+
this.sendPresetList();
|
|
139
|
+
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* saves a sample
|
|
144
|
+
* @param channel {number}
|
|
145
|
+
* @param sampleID {number}
|
|
146
|
+
* @param sampleData {Float32Array}
|
|
147
|
+
* @this {SpessaSynthProcessor}
|
|
148
|
+
*/
|
|
149
|
+
export function sampleDump(channel, sampleID, sampleData)
|
|
150
|
+
{
|
|
151
|
+
this.workletDumpedSamplesList[sampleID] = sampleData;
|
|
152
|
+
// the sample maybe was loaded after the voice was sent... adjust the end position!
|
|
153
|
+
|
|
154
|
+
// not for all channels because the system tells us for what channel this voice was dumped! yay!
|
|
155
|
+
this.workletProcessorChannels[channel].voices.forEach(v => {
|
|
156
|
+
if(v.sample.sampleID !== sampleID)
|
|
157
|
+
{
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
v.sample.end = sampleData.length - 1 + v.generators[generatorTypes.endAddrOffset] + (v.generators[generatorTypes.endAddrsCoarseOffset] * 32768);
|
|
161
|
+
// calculate for how long the sample has been playing and move the cursor there
|
|
162
|
+
v.sample.cursor = (v.sample.playbackStep * sampleRate) * (currentTime - v.startTime);
|
|
163
|
+
if(v.sample.loopingMode === 0) // no loop
|
|
164
|
+
{
|
|
165
|
+
if (v.sample.cursor >= v.sample.end)
|
|
166
|
+
{
|
|
167
|
+
v.finished = true;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else
|
|
172
|
+
{
|
|
173
|
+
// go through modulo (adjust cursor if the sample has looped
|
|
174
|
+
if(v.sample.cursor > v.sample.loopEnd)
|
|
175
|
+
{
|
|
176
|
+
v.sample.cursor = v.sample.cursor % (v.sample.loopEnd - v.sample.loopStart) + v.sample.loopStart - 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// set start time to current!
|
|
180
|
+
v.startTime = currentTime;
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { consoleColors } from '../../../utils/other.js'
|
|
2
|
+
import { midiControllers } from '../../../midi_parser/midi_message.js'
|
|
3
|
+
import { DEFAULT_PERCUSSION, DEFAULT_SYNTH_MODE } from '../../synthetizer.js'
|
|
4
|
+
import {
|
|
5
|
+
customControllers,
|
|
6
|
+
customResetArray, dataEntryStates,
|
|
7
|
+
NON_CC_INDEX_OFFSET,
|
|
8
|
+
resetArray,
|
|
9
|
+
} from '../worklet_utilities/worklet_processor_channel.js'
|
|
10
|
+
import { modulatorSources } from '../../../soundfont/read/modulators.js'
|
|
11
|
+
import { SpessaSynthInfo } from '../../../utils/loggin.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @this {SpessaSynthProcessor}
|
|
15
|
+
*/
|
|
16
|
+
export function resetAllControllers()
|
|
17
|
+
{
|
|
18
|
+
SpessaSynthInfo("%cResetting all controllers!", consoleColors.info);
|
|
19
|
+
this.callEvent("allcontrollerreset", undefined);
|
|
20
|
+
for (let channelNumber = 0; channelNumber < this.workletProcessorChannels.length; channelNumber++)
|
|
21
|
+
{
|
|
22
|
+
this.resetControllers(channelNumber);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @type {WorkletProcessorChannel}
|
|
26
|
+
**/
|
|
27
|
+
const ch = this.workletProcessorChannels[channelNumber];
|
|
28
|
+
|
|
29
|
+
// if preset is unlocked, switch to non drums and call event
|
|
30
|
+
if(!ch.lockPreset)
|
|
31
|
+
{
|
|
32
|
+
ch.midiControllers[midiControllers.bankSelect] = 0;
|
|
33
|
+
if (channelNumber % 16 === DEFAULT_PERCUSSION)
|
|
34
|
+
{
|
|
35
|
+
this.setPreset(channelNumber, this.drumPreset);
|
|
36
|
+
ch.drumChannel = true;
|
|
37
|
+
this.callEvent("drumchange", {
|
|
38
|
+
channel: channelNumber,
|
|
39
|
+
isDrumChannel: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
{
|
|
44
|
+
ch.drumChannel = false;
|
|
45
|
+
this.setPreset(channelNumber, this.defaultPreset);
|
|
46
|
+
this.callEvent("drumchange", {
|
|
47
|
+
channel: channelNumber,
|
|
48
|
+
isDrumChannel: false
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
{
|
|
54
|
+
this.callEvent("drumchange", {
|
|
55
|
+
channel: channelNumber,
|
|
56
|
+
isDrumChannel: ch.drumChannel
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// call program change
|
|
61
|
+
this.callEvent("programchange", {
|
|
62
|
+
channel: channelNumber,
|
|
63
|
+
program: ch.preset.program,
|
|
64
|
+
bank: ch.preset.bank,
|
|
65
|
+
userCalled: false
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let restoreControllerValueEvent = ccNum =>
|
|
69
|
+
{
|
|
70
|
+
if(this.workletProcessorChannels[channelNumber].lockedControllers[ccNum])
|
|
71
|
+
{
|
|
72
|
+
// was not reset so restore the value
|
|
73
|
+
this.callEvent("controllerchange", {
|
|
74
|
+
channel: channelNumber,
|
|
75
|
+
controllerNumber: ccNum,
|
|
76
|
+
controllerValue: this.workletProcessorChannels[channelNumber].midiControllers[ccNum] >> 7
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
restoreControllerValueEvent(midiControllers.mainVolume);
|
|
83
|
+
restoreControllerValueEvent(midiControllers.pan);
|
|
84
|
+
restoreControllerValueEvent(midiControllers.expressionController);
|
|
85
|
+
restoreControllerValueEvent(midiControllers.modulationWheel);
|
|
86
|
+
restoreControllerValueEvent(midiControllers.effects3Depth);
|
|
87
|
+
restoreControllerValueEvent(midiControllers.effects1Depth);
|
|
88
|
+
|
|
89
|
+
// restore pitch wheel
|
|
90
|
+
if(this.workletProcessorChannels[channelNumber].lockedControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel])
|
|
91
|
+
{
|
|
92
|
+
const val = this.workletProcessorChannels[channelNumber].midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel];
|
|
93
|
+
const msb = val >> 7;
|
|
94
|
+
const lsb = val & 0x7F;
|
|
95
|
+
this.callEvent("pitchwheel", {
|
|
96
|
+
channel: channelNumber,
|
|
97
|
+
MSB: msb,
|
|
98
|
+
LSB: lsb
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
this.setMIDIVolume(1);
|
|
103
|
+
this.system = DEFAULT_SYNTH_MODE;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resets all controllers for channel
|
|
108
|
+
* @param channel {number}
|
|
109
|
+
* @this {SpessaSynthProcessor}
|
|
110
|
+
*/
|
|
111
|
+
export function resetControllers(channel)
|
|
112
|
+
{
|
|
113
|
+
const channelObject = this.workletProcessorChannels[channel];
|
|
114
|
+
/**
|
|
115
|
+
* get excluded (locked) cc numbers as locked ccs are unaffected by reset
|
|
116
|
+
* @type {number[]}
|
|
117
|
+
*/
|
|
118
|
+
const excludedCCs = channelObject.lockedControllers.reduce((lockedCCs, cc, ccNum) => {
|
|
119
|
+
if(cc)
|
|
120
|
+
{
|
|
121
|
+
lockedCCs.push(ccNum);
|
|
122
|
+
}
|
|
123
|
+
return lockedCCs;
|
|
124
|
+
}, []);
|
|
125
|
+
// save excluded controllers as reset doesn't affect them
|
|
126
|
+
let excludedCCvalues = excludedCCs.map(ccNum => {
|
|
127
|
+
return {
|
|
128
|
+
ccNum: ccNum,
|
|
129
|
+
ccVal: channelObject.midiControllers[ccNum]
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// reset the array
|
|
134
|
+
channelObject.midiControllers.set(resetArray);
|
|
135
|
+
channelObject.channelVibrato = {rate: 0, depth: 0, delay: 0};
|
|
136
|
+
channelObject.holdPedal = false;
|
|
137
|
+
|
|
138
|
+
excludedCCvalues.forEach((cc) => {
|
|
139
|
+
channelObject.midiControllers[cc.ccNum] = cc.ccVal;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// reset custom controllers
|
|
143
|
+
// special case: transpose does not get affected
|
|
144
|
+
const transpose = channelObject.customControllers[customControllers.channelTransposeFine];
|
|
145
|
+
channelObject.customControllers.set(customResetArray);
|
|
146
|
+
channelObject.customControllers[customControllers.channelTransposeFine] = transpose;
|
|
147
|
+
|
|
148
|
+
this.resetParameters(channel);
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param channel {number}
|
|
154
|
+
* @this {SpessaSynthProcessor}
|
|
155
|
+
*/
|
|
156
|
+
export function resetParameters(channel)
|
|
157
|
+
{
|
|
158
|
+
const channelObject = this.workletProcessorChannels[channel];
|
|
159
|
+
|
|
160
|
+
// reset parameters
|
|
161
|
+
/**
|
|
162
|
+
* @type {number}
|
|
163
|
+
*/
|
|
164
|
+
channelObject.NRPCoarse = 0;
|
|
165
|
+
/**
|
|
166
|
+
* @type {number}
|
|
167
|
+
*/
|
|
168
|
+
channelObject.NRPFine = 0;
|
|
169
|
+
/**
|
|
170
|
+
* @type {number}
|
|
171
|
+
*/
|
|
172
|
+
channelObject.RPValue = 0;
|
|
173
|
+
/**
|
|
174
|
+
* @type {string}
|
|
175
|
+
*/
|
|
176
|
+
channelObject.dataEntryState = dataEntryStates.Idle;
|
|
177
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} ChannelSnapshot - a snapshot of the channel.
|
|
4
|
+
*
|
|
5
|
+
* @property {number} program - the channel's program
|
|
6
|
+
* @property {number} bank - the channel's bank
|
|
7
|
+
* @property {string} patchName - the channel's patch name
|
|
8
|
+
* @property {boolean} lockPreset - indicates whether the channel's program change is disabled
|
|
9
|
+
*
|
|
10
|
+
* @property {Int16Array} midiControllers - the array of all midi controllers (in 14-bit values) with the modulator sources at the end. See Synthesizer Class on wiki (lockController section)
|
|
11
|
+
* @property {boolean[]} lockedControllers - an array of booleans, indicating if the controller with a current index is locked
|
|
12
|
+
* @property {Float32Array} customControllers - array of custom (not sf2) control values such as RPN pitch tuning, transpose, modulation depth, etc.
|
|
13
|
+
*
|
|
14
|
+
* // note: this is a custom vibrato object, set by NRPN messages
|
|
15
|
+
* @property {boolean} lockVibrato - indicates whether the channel vibrato is locked
|
|
16
|
+
* @property {Object} channelVibrato - the channel's vibrato
|
|
17
|
+
* @property {number} channelVibrato.depth - vibrato depth, in gain
|
|
18
|
+
* @property {number} channelVibrato.delay - vibrato delay from note on in seconds
|
|
19
|
+
* @property {number} channelVibrato.rate - vibrato rate in Hz
|
|
20
|
+
*
|
|
21
|
+
* @property {number} channelTransposeKeyShift - key shift for the channel
|
|
22
|
+
* @property {boolean} isMuted - indicates whether the channel is muted
|
|
23
|
+
* @property {boolean} drumChannel - indicates whether the channel is a drum channel
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} SynthesizerSnapshot
|
|
27
|
+
* @property {ChannelSnapshot[]} channelSnapshots - the individual channel snapshots
|
|
28
|
+
* @property {number} mainVolume - main synth volume (set by MIDI), from 0 to 1
|
|
29
|
+
* @property {number} pan - master stereo panning, from -1 to 1
|
|
30
|
+
* @property {SynthSystem} system - the synths system. Values can be "gs", "gm", "gm2" or "xg"
|
|
31
|
+
* @property {number} transposition - the current synth transpositon in semitones. can be a float
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { returnMessageType } from '../message_protocol/worklet_message.js'
|
|
35
|
+
import { SpessaSynthInfo } from '../../../utils/loggin.js'
|
|
36
|
+
import { consoleColors } from '../../../utils/other.js'
|
|
37
|
+
import { midiControllers } from '../../../midi_parser/midi_message.js'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* sends a snapshot of the current controller values of the synth (used to copy that data to OfflineAudioContext when rendering)
|
|
41
|
+
* @this {SpessaSynthProcessor}
|
|
42
|
+
*/
|
|
43
|
+
export function sendSynthesizerSnapshot()
|
|
44
|
+
{
|
|
45
|
+
/**
|
|
46
|
+
* @type {ChannelSnapshot[]}
|
|
47
|
+
*/
|
|
48
|
+
const channelSnapshots = this.workletProcessorChannels.map(channel => {
|
|
49
|
+
return {
|
|
50
|
+
program: channel.preset.program,
|
|
51
|
+
bank: channel.preset.bank,
|
|
52
|
+
lockPreset: channel.lockPreset,
|
|
53
|
+
patchName: channel.preset.presetName,
|
|
54
|
+
|
|
55
|
+
midiControllers: channel.midiControllers,
|
|
56
|
+
lockedControllers: channel.lockedControllers,
|
|
57
|
+
customControllers: channel.customControllers,
|
|
58
|
+
|
|
59
|
+
channelVibrato: channel.channelVibrato,
|
|
60
|
+
lockVibrato: channel.lockVibrato,
|
|
61
|
+
|
|
62
|
+
channelTransposeKeyShift: channel.channelTransposeKeyShift,
|
|
63
|
+
isMuted: channel.isMuted,
|
|
64
|
+
drumChannel: channel.drumChannel
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @type {SynthesizerSnapshot}
|
|
70
|
+
*/
|
|
71
|
+
const synthesizerSnapshot = {
|
|
72
|
+
channelSnapshots: channelSnapshots,
|
|
73
|
+
mainVolume: this.midiVolume,
|
|
74
|
+
pan: this.pan,
|
|
75
|
+
transposition: this.transposition,
|
|
76
|
+
system: this.system
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
this.post({
|
|
80
|
+
messageType: returnMessageType.synthesizerSnapshot,
|
|
81
|
+
messageData: synthesizerSnapshot
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Applies the snapshot to the synth
|
|
87
|
+
* @param snapshot {SynthesizerSnapshot}
|
|
88
|
+
* @this {SpessaSynthProcessor}
|
|
89
|
+
*/
|
|
90
|
+
export function applySynthesizerSnapshot(snapshot)
|
|
91
|
+
{
|
|
92
|
+
// restore system
|
|
93
|
+
this.system = snapshot.system;
|
|
94
|
+
|
|
95
|
+
// restore pan and volume
|
|
96
|
+
this.setMasterGain(snapshot.mainVolume);
|
|
97
|
+
this.setMasterPan(snapshot.pan);
|
|
98
|
+
this.transposeAllChannels(snapshot.transposition);
|
|
99
|
+
|
|
100
|
+
// add channels if more needed
|
|
101
|
+
while(this.workletProcessorChannels.length < snapshot.channelSnapshots.length)
|
|
102
|
+
{
|
|
103
|
+
this.createWorkletChannel();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// restore cahnnels
|
|
107
|
+
snapshot.channelSnapshots.forEach((channelSnapshot, index) => {
|
|
108
|
+
const channelObject = this.workletProcessorChannels[index];
|
|
109
|
+
this.muteChannel(index, channelSnapshot.isMuted);
|
|
110
|
+
this.setDrums(index, channelSnapshot.drumChannel);
|
|
111
|
+
|
|
112
|
+
// restore controllers
|
|
113
|
+
channelObject.midiControllers = channelSnapshot.midiControllers;
|
|
114
|
+
channelObject.lockedControllers = channelSnapshot.lockedControllers;
|
|
115
|
+
channelObject.customControllers = channelSnapshot.customControllers;
|
|
116
|
+
|
|
117
|
+
// restore vibrato and transpose
|
|
118
|
+
channelObject.channelVibrato = channelSnapshot.channelVibrato;
|
|
119
|
+
channelObject.lockVibrato = channelSnapshot.lockVibrato;
|
|
120
|
+
channelObject.channelTransposeKeyShift = channelSnapshot.channelTransposeKeyShift;
|
|
121
|
+
|
|
122
|
+
// restore preset and lock
|
|
123
|
+
channelObject.lockPreset = false;
|
|
124
|
+
channelObject.midiControllers[midiControllers.bankSelect] = channelSnapshot.bank;
|
|
125
|
+
this.programChange(index, channelSnapshot.program);
|
|
126
|
+
channelObject.lockPreset = channelSnapshot.lockPreset;
|
|
127
|
+
});
|
|
128
|
+
SpessaSynthInfo("%cFinished restoring controllers!", consoleColors.info);
|
|
129
|
+
}
|