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.
Files changed (154) hide show
  1. package/.idea/modules.xml +8 -0
  2. package/.idea/spessasynth_lib.iml +12 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/copy_version.sh +38 -0
  5. package/index.js +73 -0
  6. package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  7. package/package/@types/index.d.ts +34 -0
  8. package/package/@types/midi_handler/midi_handler.d.ts +39 -0
  9. package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
  10. package/package/@types/midi_parser/midi_data.d.ts +95 -0
  11. package/package/@types/midi_parser/midi_editor.d.ts +45 -0
  12. package/package/@types/midi_parser/midi_loader.d.ts +100 -0
  13. package/package/@types/midi_parser/midi_message.d.ts +154 -0
  14. package/package/@types/midi_parser/midi_writer.d.ts +6 -0
  15. package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  16. package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  17. package/package/@types/sequencer/sequencer.d.ts +180 -0
  18. package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  19. package/package/@types/soundfont/read/generators.d.ts +98 -0
  20. package/package/@types/soundfont/read/instruments.d.ts +50 -0
  21. package/package/@types/soundfont/read/modulators.d.ts +73 -0
  22. package/package/@types/soundfont/read/presets.d.ts +87 -0
  23. package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  24. package/package/@types/soundfont/read/samples.d.ts +134 -0
  25. package/package/@types/soundfont/read/zones.d.ts +141 -0
  26. package/package/@types/soundfont/soundfont.d.ts +76 -0
  27. package/package/@types/soundfont/write/ibag.d.ts +6 -0
  28. package/package/@types/soundfont/write/igen.d.ts +6 -0
  29. package/package/@types/soundfont/write/imod.d.ts +6 -0
  30. package/package/@types/soundfont/write/inst.d.ts +6 -0
  31. package/package/@types/soundfont/write/pbag.d.ts +6 -0
  32. package/package/@types/soundfont/write/pgen.d.ts +6 -0
  33. package/package/@types/soundfont/write/phdr.d.ts +6 -0
  34. package/package/@types/soundfont/write/pmod.d.ts +6 -0
  35. package/package/@types/soundfont/write/sdta.d.ts +11 -0
  36. package/package/@types/soundfont/write/shdr.d.ts +8 -0
  37. package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  38. package/package/@types/soundfont/write/write.d.ts +21 -0
  39. package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  40. package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  41. package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  42. package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  43. package/package/@types/synthetizer/synthetizer.d.ts +294 -0
  44. package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
  45. package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  46. package/package/@types/synthetizer/worklet_url.d.ts +5 -0
  47. package/package/@types/utils/buffer_to_wav.d.ts +8 -0
  48. package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  49. package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  50. package/package/@types/utils/byte_functions/string.d.ts +22 -0
  51. package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  52. package/package/@types/utils/indexed_array.d.ts +21 -0
  53. package/package/@types/utils/loggin.d.ts +26 -0
  54. package/package/@types/utils/other.d.ts +32 -0
  55. package/package/LICENSE +26 -0
  56. package/package/README.md +84 -0
  57. package/package/externals/NOTICE +9 -0
  58. package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  59. package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  60. package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  61. package/package/externals/stbvorbis_sync/LICENSE +202 -0
  62. package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  63. package/package/index.js +73 -0
  64. package/package/midi_handler/README.md +3 -0
  65. package/package/midi_handler/midi_handler.js +118 -0
  66. package/package/midi_handler/web_midi_link.js +41 -0
  67. package/package/midi_parser/README.md +3 -0
  68. package/package/midi_parser/midi_data.js +121 -0
  69. package/package/midi_parser/midi_editor.js +557 -0
  70. package/package/midi_parser/midi_loader.js +502 -0
  71. package/package/midi_parser/midi_message.js +234 -0
  72. package/package/midi_parser/midi_writer.js +95 -0
  73. package/package/midi_parser/rmidi_writer.js +271 -0
  74. package/package/midi_parser/used_keys_loaded.js +172 -0
  75. package/package/package.json +43 -0
  76. package/package/sequencer/README.md +23 -0
  77. package/package/sequencer/sequencer.js +439 -0
  78. package/package/sequencer/worklet_sequencer/events.js +92 -0
  79. package/package/sequencer/worklet_sequencer/play.js +309 -0
  80. package/package/sequencer/worklet_sequencer/process_event.js +167 -0
  81. package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
  82. package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  83. package/package/sequencer/worklet_sequencer/song_control.js +193 -0
  84. package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  85. package/package/soundfont/README.md +8 -0
  86. package/package/soundfont/read/generators.js +212 -0
  87. package/package/soundfont/read/instruments.js +125 -0
  88. package/package/soundfont/read/modulators.js +249 -0
  89. package/package/soundfont/read/presets.js +300 -0
  90. package/package/soundfont/read/riff_chunk.js +81 -0
  91. package/package/soundfont/read/samples.js +398 -0
  92. package/package/soundfont/read/zones.js +310 -0
  93. package/package/soundfont/soundfont.js +357 -0
  94. package/package/soundfont/write/ibag.js +39 -0
  95. package/package/soundfont/write/igen.js +75 -0
  96. package/package/soundfont/write/imod.js +46 -0
  97. package/package/soundfont/write/inst.js +34 -0
  98. package/package/soundfont/write/pbag.js +39 -0
  99. package/package/soundfont/write/pgen.js +77 -0
  100. package/package/soundfont/write/phdr.js +42 -0
  101. package/package/soundfont/write/pmod.js +46 -0
  102. package/package/soundfont/write/sdta.js +72 -0
  103. package/package/soundfont/write/shdr.js +54 -0
  104. package/package/soundfont/write/soundfont_trimmer.js +169 -0
  105. package/package/soundfont/write/write.js +180 -0
  106. package/package/synthetizer/README.md +6 -0
  107. package/package/synthetizer/audio_effects/effects_config.js +21 -0
  108. package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  109. package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  110. package/package/synthetizer/audio_effects/reverb.js +24 -0
  111. package/package/synthetizer/synth_event_handler.js +156 -0
  112. package/package/synthetizer/synthetizer.js +766 -0
  113. package/package/synthetizer/worklet_processor.min.js +13 -0
  114. package/package/synthetizer/worklet_system/README.md +6 -0
  115. package/package/synthetizer/worklet_system/main_processor.js +363 -0
  116. package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
  117. package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  118. package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
  119. package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
  120. package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  121. package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  122. package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  123. package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  124. package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  125. package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  126. package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  127. package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  128. package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  129. package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  130. package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  131. package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
  132. package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  133. package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  134. package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  135. package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  136. package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  137. package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  138. package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  139. package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  140. package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  141. package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  142. package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  143. package/package/synthetizer/worklet_url.js +5 -0
  144. package/package/utils/README.md +4 -0
  145. package/package/utils/buffer_to_wav.js +101 -0
  146. package/package/utils/byte_functions/big_endian.js +28 -0
  147. package/package/utils/byte_functions/little_endian.js +74 -0
  148. package/package/utils/byte_functions/string.js +97 -0
  149. package/package/utils/byte_functions/variable_length_quantity.js +37 -0
  150. package/package/utils/encode_vorbis.js +30 -0
  151. package/package/utils/indexed_array.js +41 -0
  152. package/package/utils/loggin.js +79 -0
  153. package/package/utils/other.js +54 -0
  154. 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
+ }