spessasynth_lib 3.9.9

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 (148) hide show
  1. package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  2. package/@types/index.d.ts +34 -0
  3. package/@types/midi_handler/midi_handler.d.ts +39 -0
  4. package/@types/midi_handler/web_midi_link.d.ts +12 -0
  5. package/@types/midi_parser/midi_data.d.ts +95 -0
  6. package/@types/midi_parser/midi_editor.d.ts +45 -0
  7. package/@types/midi_parser/midi_loader.d.ts +100 -0
  8. package/@types/midi_parser/midi_message.d.ts +154 -0
  9. package/@types/midi_parser/midi_writer.d.ts +6 -0
  10. package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  11. package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  12. package/@types/sequencer/sequencer.d.ts +180 -0
  13. package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  14. package/@types/soundfont/read/generators.d.ts +98 -0
  15. package/@types/soundfont/read/instruments.d.ts +50 -0
  16. package/@types/soundfont/read/modulators.d.ts +73 -0
  17. package/@types/soundfont/read/presets.d.ts +87 -0
  18. package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  19. package/@types/soundfont/read/samples.d.ts +134 -0
  20. package/@types/soundfont/read/zones.d.ts +141 -0
  21. package/@types/soundfont/soundfont.d.ts +76 -0
  22. package/@types/soundfont/write/ibag.d.ts +6 -0
  23. package/@types/soundfont/write/igen.d.ts +6 -0
  24. package/@types/soundfont/write/imod.d.ts +6 -0
  25. package/@types/soundfont/write/inst.d.ts +6 -0
  26. package/@types/soundfont/write/pbag.d.ts +6 -0
  27. package/@types/soundfont/write/pgen.d.ts +6 -0
  28. package/@types/soundfont/write/phdr.d.ts +6 -0
  29. package/@types/soundfont/write/pmod.d.ts +6 -0
  30. package/@types/soundfont/write/sdta.d.ts +11 -0
  31. package/@types/soundfont/write/shdr.d.ts +8 -0
  32. package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  33. package/@types/soundfont/write/write.d.ts +21 -0
  34. package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  35. package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  36. package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  37. package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  38. package/@types/synthetizer/synthetizer.d.ts +286 -0
  39. package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +88 -0
  40. package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  41. package/@types/synthetizer/worklet_url.d.ts +5 -0
  42. package/@types/utils/buffer_to_wav.d.ts +8 -0
  43. package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  44. package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  45. package/@types/utils/byte_functions/string.d.ts +22 -0
  46. package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  47. package/@types/utils/indexed_array.d.ts +21 -0
  48. package/@types/utils/loggin.d.ts +26 -0
  49. package/@types/utils/other.d.ts +32 -0
  50. package/LICENSE +26 -0
  51. package/README.md +83 -0
  52. package/externals/NOTICE +9 -0
  53. package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  54. package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  55. package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  56. package/externals/stbvorbis_sync/LICENSE +202 -0
  57. package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  58. package/index.js +73 -0
  59. package/midi_handler/README.md +3 -0
  60. package/midi_handler/midi_handler.js +118 -0
  61. package/midi_handler/web_midi_link.js +41 -0
  62. package/midi_parser/README.md +3 -0
  63. package/midi_parser/midi_data.js +121 -0
  64. package/midi_parser/midi_editor.js +557 -0
  65. package/midi_parser/midi_loader.js +502 -0
  66. package/midi_parser/midi_message.js +234 -0
  67. package/midi_parser/midi_writer.js +95 -0
  68. package/midi_parser/rmidi_writer.js +271 -0
  69. package/midi_parser/used_keys_loaded.js +172 -0
  70. package/package.json +44 -0
  71. package/sequencer/README.md +23 -0
  72. package/sequencer/sequencer.js +439 -0
  73. package/sequencer/worklet_sequencer/events.js +92 -0
  74. package/sequencer/worklet_sequencer/play.js +309 -0
  75. package/sequencer/worklet_sequencer/process_event.js +167 -0
  76. package/sequencer/worklet_sequencer/process_tick.js +85 -0
  77. package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  78. package/sequencer/worklet_sequencer/song_control.js +193 -0
  79. package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  80. package/soundfont/README.md +8 -0
  81. package/soundfont/read/generators.js +212 -0
  82. package/soundfont/read/instruments.js +125 -0
  83. package/soundfont/read/modulators.js +249 -0
  84. package/soundfont/read/presets.js +300 -0
  85. package/soundfont/read/riff_chunk.js +81 -0
  86. package/soundfont/read/samples.js +398 -0
  87. package/soundfont/read/zones.js +310 -0
  88. package/soundfont/soundfont.js +357 -0
  89. package/soundfont/write/ibag.js +39 -0
  90. package/soundfont/write/igen.js +75 -0
  91. package/soundfont/write/imod.js +46 -0
  92. package/soundfont/write/inst.js +34 -0
  93. package/soundfont/write/pbag.js +39 -0
  94. package/soundfont/write/pgen.js +77 -0
  95. package/soundfont/write/phdr.js +42 -0
  96. package/soundfont/write/pmod.js +46 -0
  97. package/soundfont/write/sdta.js +72 -0
  98. package/soundfont/write/shdr.js +54 -0
  99. package/soundfont/write/soundfont_trimmer.js +169 -0
  100. package/soundfont/write/write.js +180 -0
  101. package/synthetizer/README.md +6 -0
  102. package/synthetizer/audio_effects/effects_config.js +21 -0
  103. package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  104. package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  105. package/synthetizer/audio_effects/reverb.js +24 -0
  106. package/synthetizer/synth_event_handler.js +156 -0
  107. package/synthetizer/synthetizer.js +750 -0
  108. package/synthetizer/worklet_processor.min.js +13 -0
  109. package/synthetizer/worklet_system/README.md +6 -0
  110. package/synthetizer/worklet_system/main_processor.js +363 -0
  111. package/synthetizer/worklet_system/message_protocol/handle_message.js +193 -0
  112. package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  113. package/synthetizer/worklet_system/message_protocol/worklet_message.js +118 -0
  114. package/synthetizer/worklet_system/minify_processor.sh +4 -0
  115. package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  116. package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  117. package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  118. package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  119. package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  120. package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  121. package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  122. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  123. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  124. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  125. package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  126. package/synthetizer/worklet_system/worklet_processor.js +9 -0
  127. package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  128. package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  129. package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  130. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  131. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  132. package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  133. package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  134. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  135. package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  136. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  137. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  138. package/synthetizer/worklet_url.js +5 -0
  139. package/utils/README.md +4 -0
  140. package/utils/buffer_to_wav.js +101 -0
  141. package/utils/byte_functions/big_endian.js +28 -0
  142. package/utils/byte_functions/little_endian.js +74 -0
  143. package/utils/byte_functions/string.js +97 -0
  144. package/utils/byte_functions/variable_length_quantity.js +37 -0
  145. package/utils/encode_vorbis.js +30 -0
  146. package/utils/indexed_array.js +41 -0
  147. package/utils/loggin.js +79 -0
  148. package/utils/other.js +54 -0
@@ -0,0 +1,233 @@
1
+ import { generatorTypes } from '../../../soundfont/read/generators.js'
2
+ import { absCentsToHz, decibelAttenuationToGain, timecentsToSeconds } from '../worklet_utilities/unit_converter.js'
3
+ import { getLFOValue } from '../worklet_utilities/lfo.js'
4
+ import { customControllers } from '../worklet_utilities/worklet_processor_channel.js'
5
+ import { getModEnvValue } from '../worklet_utilities/modulation_envelope.js'
6
+ import { getOscillatorData } from '../worklet_utilities/wavetable_oscillator.js'
7
+ import { panVoice } from '../worklet_utilities/stereo_panner.js'
8
+ import { applyVolumeEnvelope, recalculateVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
9
+ import { applyLowpassFilter } from '../worklet_utilities/lowpass_filter.js'
10
+ import { MIN_NOTE_LENGTH } from '../main_processor.js'
11
+
12
+
13
+ const HALF_PI = Math.PI / 2;
14
+ export const PAN_SMOOTHING_FACTOR = 0.01;
15
+ /**
16
+ * Renders a voice to the stereo output buffer
17
+ * @param channel {WorkletProcessorChannel} the voice's channel
18
+ * @param voice {WorkletVoice} the voice to render
19
+ * @param outputLeft {Float32Array} the left output buffer
20
+ * @param outputRight {Float32Array} the right output buffer
21
+ * @param reverbOutput {Float32Array[]} output for reverb
22
+ * @param chorusOutput {Float32Array[]} output for chorus
23
+ * @this {SpessaSynthProcessor}
24
+ */
25
+ export function renderVoice(
26
+ channel,
27
+ voice,
28
+ outputLeft, outputRight,
29
+ reverbOutput,
30
+ chorusOutput
31
+ )
32
+ {
33
+ // check if release
34
+ if(!voice.isInRelease)
35
+ {
36
+ // if not in release, check if the release time is
37
+ if (currentTime >= voice.releaseStartTime)
38
+ {
39
+ voice.releaseStartModEnv = voice.currentModEnvValue;
40
+ voice.isInRelease = true;
41
+ recalculateVolumeEnvelope(voice);
42
+ voice.volumeEnvelope.currentReleaseGain = decibelAttenuationToGain(voice.volumeEnvelope.currentAttenuationDb);
43
+ }
44
+ }
45
+
46
+
47
+ // if the initial attenuation is more than 100dB, skip the voice (it's silent anyways)
48
+ if(voice.modulatedGenerators[generatorTypes.initialAttenuation] > 2500)
49
+ {
50
+ if(voice.isInRelease)
51
+ {
52
+ voice.finished = true;
53
+ }
54
+ return;
55
+ }
56
+
57
+ // TUNING
58
+
59
+ // calculate tuning
60
+ let cents = voice.modulatedGenerators[generatorTypes.fineTune]
61
+ + channel.customControllers[customControllers.channelTuning]
62
+ + channel.customControllers[customControllers.channelTransposeFine]
63
+ + channel.customControllers[customControllers.masterTuning];
64
+ let semitones = voice.modulatedGenerators[generatorTypes.coarseTune]
65
+ + channel.customControllers[customControllers.channelTuningSemitones];
66
+
67
+ // calculate tuning by key
68
+ cents += (voice.targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
69
+
70
+ // vibrato LFO
71
+ const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
72
+ if(vibratoDepth !== 0)
73
+ {
74
+ const vibStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVibLFO]);
75
+ const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
76
+ const lfoVal = getLFOValue(vibStart, vibFreqHz, currentTime);
77
+ if(lfoVal)
78
+ {
79
+ cents += lfoVal * (vibratoDepth * channel.customControllers[customControllers.modulationMultiplier]);
80
+ }
81
+ }
82
+
83
+ // lowpass frequency
84
+ let lowpassCents = voice.modulatedGenerators[generatorTypes.initialFilterFc];
85
+
86
+ // mod LFO
87
+ const modPitchDepth = voice.modulatedGenerators[generatorTypes.modLfoToPitch];
88
+ const modVolDepth = voice.modulatedGenerators[generatorTypes.modLfoToVolume];
89
+ const modFilterDepth = voice.modulatedGenerators[generatorTypes.modLfoToFilterFc];
90
+ let modLfoCentibels = 0;
91
+ if(modPitchDepth + modFilterDepth + modVolDepth !== 0)
92
+ {
93
+ const modStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModLFO]);
94
+ const modFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqModLFO]);
95
+ const modLfoValue = getLFOValue(modStart, modFreqHz, currentTime);
96
+ cents += modLfoValue * (modPitchDepth * channel.customControllers[customControllers.modulationMultiplier]);
97
+ modLfoCentibels = modLfoValue * modVolDepth;
98
+ lowpassCents += modLfoValue * modFilterDepth;
99
+ }
100
+
101
+ // channel vibrato (GS NRPN)
102
+ if(channel.channelVibrato.depth > 0)
103
+ {
104
+ const channelVibrato = getLFOValue(voice.startTime + channel.channelVibrato.delay, channel.channelVibrato.rate, currentTime);
105
+ if(channelVibrato)
106
+ {
107
+ cents += channelVibrato * channel.channelVibrato.depth;
108
+ }
109
+ }
110
+
111
+ // mod env
112
+ const modEnvPitchDepth = voice.modulatedGenerators[generatorTypes.modEnvToPitch];
113
+ const modEnvFilterDepth = voice.modulatedGenerators[generatorTypes.modEnvToFilterFc];
114
+ const modEnv = getModEnvValue(voice, currentTime);
115
+ lowpassCents += modEnv * modEnvFilterDepth;
116
+ cents += modEnv * modEnvPitchDepth;
117
+
118
+ // finally calculate the playback rate
119
+ const centsTotal = ~~(cents + semitones * 100);
120
+ if(centsTotal !== voice.currentTuningCents)
121
+ {
122
+ voice.currentTuningCents = centsTotal;
123
+ voice.currentTuningCalculated = Math.pow(2, centsTotal / 1200);
124
+ }
125
+
126
+ // PANNING
127
+ const pan = ((Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan] )) + 500) / 1000) ; // 0 to 1
128
+
129
+ // SYNTHESIS
130
+ const bufferOut = new Float32Array(outputLeft.length);
131
+
132
+ // wavetable oscillator
133
+ getOscillatorData(voice, this.workletDumpedSamplesList[voice.sample.sampleID], bufferOut);
134
+
135
+ // lowpass filter
136
+ applyLowpassFilter(voice, bufferOut, lowpassCents);
137
+
138
+ // volenv
139
+ applyVolumeEnvelope(voice, bufferOut, currentTime, modLfoCentibels, this.sampleTime, this.volumeEnvelopeSmoothingFactor);
140
+
141
+ // pan the voice and write out
142
+ voice.currentPan += (pan - voice.currentPan) * this.panSmoothingFactor; // smooth out pan to prevent clicking
143
+ const panLeft = Math.cos(HALF_PI * voice.currentPan) * this.panLeft;
144
+ const panRight = Math.sin(HALF_PI * voice.currentPan) * this.panRight;
145
+ const reverb = this.oneOutputMode ? 0 : voice.modulatedGenerators[generatorTypes.reverbEffectsSend];
146
+ const chorus = this.oneOutputMode ? 0 : voice.modulatedGenerators[generatorTypes.chorusEffectsSend];
147
+ panVoice(
148
+ panLeft,
149
+ panRight,
150
+ bufferOut,
151
+ outputLeft, outputRight,
152
+ reverbOutput, reverb,
153
+ chorusOutput, chorus);
154
+ }
155
+
156
+
157
+ /**
158
+ * @param channel {WorkletProcessorChannel}
159
+ * @param voice {WorkletVoice}
160
+ * @return {number}
161
+ */
162
+ function getPriority(channel, voice)
163
+ {
164
+ let priority = 0;
165
+ if(channel.drumChannel)
166
+ {
167
+ // important
168
+ priority += 5;
169
+ }
170
+ if(voice.isInRelease)
171
+ {
172
+ // not important
173
+ priority -= 5;
174
+ }
175
+ // less velocity = less important
176
+ priority += voice.velocity / 25; // map to 0-5
177
+ // the newer, more important
178
+ priority -= voice.volumeEnvelope.state;
179
+ if(voice.isInRelease)
180
+ {
181
+ priority -= 5;
182
+ }
183
+ priority -= voice.volumeEnvelope.currentAttenuationDb / 50;
184
+ return priority;
185
+ }
186
+
187
+ /**
188
+ * @this {SpessaSynthProcessor}
189
+ * @param amount {number}
190
+ */
191
+ export function voiceKilling(amount)
192
+ {
193
+ let allVoices = [];
194
+ for (const channel of this.workletProcessorChannels)
195
+ {
196
+ for (const voice of channel.voices)
197
+ {
198
+ if (!voice.finished)
199
+ {
200
+ const priority = getPriority(channel, voice);
201
+ allVoices.push({ channel, voice, priority });
202
+ }
203
+ }
204
+ }
205
+
206
+ // Step 2: Sort voices by priority (ascending order)
207
+ allVoices.sort((a, b) => a.priority - b.priority);
208
+ const voicesToRemove = allVoices.slice(0, amount);
209
+
210
+ for (const { channel, voice } of voicesToRemove)
211
+ {
212
+ const index = channel.voices.indexOf(voice);
213
+ if (index > -1)
214
+ {
215
+ channel.voices.splice(index, 1);
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Stops the voice
222
+ * @param voice {WorkletVoice} the voice to stop
223
+ * @this {SpessaSynthProcessor}
224
+ */
225
+ export function releaseVoice(voice)
226
+ {
227
+ voice.releaseStartTime = currentTime;
228
+ // check if the note is shorter than the min note time, if so, extend it
229
+ if(voice.releaseStartTime - voice.startTime < MIN_NOTE_LENGTH)
230
+ {
231
+ voice.releaseStartTime = voice.startTime + MIN_NOTE_LENGTH;
232
+ }
233
+ }
@@ -0,0 +1,9 @@
1
+ import { WORKLET_PROCESSOR_NAME } from '../synthetizer.js'
2
+ import { consoleColors } from '../../utils/other.js'
3
+ import { SpessaSynthProcessor } from './main_processor.js'
4
+ import { SpessaSynthInfo } from '../../utils/loggin.js'
5
+
6
+
7
+ // noinspection JSUnresolvedReference
8
+ registerProcessor(WORKLET_PROCESSOR_NAME, SpessaSynthProcessor);
9
+ SpessaSynthInfo("%cProcessor succesfully registered!", consoleColors.recognized);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * lfo.js
3
+ * purpose: low frequency triangel oscillator
4
+ */
5
+
6
+ /**
7
+ * Calculates a triangular wave value for the given time
8
+ * @param startTime {number} seconds
9
+ * @param frequency {number} Hz
10
+ * @param currentTime {number} seconds
11
+ * @return {number} the value from -1 to 1
12
+ */
13
+ export function getLFOValue(startTime, frequency, currentTime) {
14
+ if (currentTime < startTime) {
15
+ return 0;
16
+ }
17
+
18
+ const xVal = (currentTime - startTime) / (1 / frequency) - 0.25;
19
+ // offset by -0.25, otherwise we start at -1 and can have unexpected jump in pitch or lowpass (happened with Synth Strings 2)
20
+
21
+ // triangle, not sine
22
+ return Math.abs(xVal - (~~(xVal + 0.5))) * 4 - 1;
23
+ }
@@ -0,0 +1,130 @@
1
+ import { generatorTypes } from '../../../soundfont/read/generators.js'
2
+ import { absCentsToHz, decibelAttenuationToGain } from './unit_converter.js'
3
+
4
+ /**
5
+ * lowpass_filter.js
6
+ * purpose: applies a low pass filter to a voice
7
+ * note to self: most of this is code is just javascript version of the C code from fluidsynth,
8
+ * they are the real smart guys.
9
+ * Shoutout to them!
10
+ */
11
+
12
+
13
+ /**
14
+ * @typedef {Object} WorkletLowpassFilter
15
+ * @property {number} a0 - filter coefficient 1
16
+ * @property {number} a1 - filter coefficient 2
17
+ * @property {number} a2 - filter coefficient 3
18
+ * @property {number} a3 - filter coefficient 4
19
+ * @property {number} a4 - filter coefficient 5
20
+ * @property {number} x1 - input history 1
21
+ * @property {number} x2 - input history 2
22
+ * @property {number} y1 - output history 1
23
+ * @property {number} y2 - output history 2
24
+ * @property {number} reasonanceCb - reasonance in centibels
25
+ * @property {number} reasonanceGain - resonance gain
26
+ * @property {number} cutoffCents - cutoff frequency in cents
27
+ * @property {number} cutoffHz - cutoff frequency in Hz
28
+ */
29
+
30
+ /**
31
+ * @type {WorkletLowpassFilter}
32
+ */
33
+ export const DEFAULT_WORKLET_LOWPASS_FILTER = {
34
+ a0: 0,
35
+ a1: 0,
36
+ a2: 0,
37
+ a3: 0,
38
+ a4: 0,
39
+
40
+ x1: 0,
41
+ x2: 0,
42
+ y1: 0,
43
+ y2: 0,
44
+
45
+ reasonanceCb: 0,
46
+ reasonanceGain: 1,
47
+ cutoffCents: 13500,
48
+ cutoffHz: 20000
49
+ }
50
+
51
+ /**
52
+ * Applies a low-pass filter to the given buffer
53
+ * @param voice {WorkletVoice} the voice we're working on
54
+ * @param outputBuffer {Float32Array} the buffer to apply the filter to
55
+ * @param cutoffCents {number} cutoff frequency in cents
56
+ */
57
+ export function applyLowpassFilter(voice, outputBuffer, cutoffCents)
58
+ {
59
+ if(cutoffCents > 13499)
60
+ {
61
+ return; // filter is open
62
+ }
63
+
64
+ // check if the frequency has changed. if so, calculate new coefficients
65
+ if(voice.filter.cutoffCents !== cutoffCents || voice.filter.reasonanceCb !== voice.modulatedGenerators[generatorTypes.initialFilterQ])
66
+ {
67
+ voice.filter.cutoffCents = cutoffCents;
68
+ voice.filter.reasonanceCb = voice.modulatedGenerators[generatorTypes.initialFilterQ];
69
+ calculateCoefficients(voice);
70
+ }
71
+
72
+ // filter the input
73
+ for (let i = 0; i < outputBuffer.length; i++) {
74
+ let input = outputBuffer[i];
75
+ let filtered = voice.filter.a0 * input
76
+ + voice.filter.a1 * voice.filter.x1
77
+ + voice.filter.a2 * voice.filter.x2
78
+ - voice.filter.a3 * voice.filter.y1
79
+ - voice.filter.a4 * voice.filter.y2;
80
+
81
+ // set buffer
82
+ voice.filter.x2 = voice.filter.x1;
83
+ voice.filter.x1 = input;
84
+ voice.filter.y2 = voice.filter.y1;
85
+ voice.filter.y1 = filtered;
86
+
87
+ outputBuffer[i] = filtered;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * @param voice {WorkletVoice}
93
+ */
94
+ function calculateCoefficients(voice)
95
+ {
96
+ voice.filter.cutoffHz = absCentsToHz(voice.filter.cutoffCents);
97
+
98
+ // fix cutoff on low frequencies (fluid_iir_filter.c line 392)
99
+ if(voice.filter.cutoffHz > 0.45 * sampleRate)
100
+ {
101
+ voice.filter.cutoffHz = 0.45 * sampleRate;
102
+ }
103
+
104
+ // adjust the filterQ (fluid_iir_filter.c line 204)
105
+ const qDb = (voice.filter.reasonanceCb / 10) - 3.01;
106
+ voice.filter.reasonanceGain = decibelAttenuationToGain(-1 * qDb); // -1 because it's attenuation and we don't want attenuation
107
+
108
+ // reduce the gain by the Q factor (fluid_iir_filter.c line 250)
109
+ const qGain = 1 / Math.sqrt(voice.filter.reasonanceGain);
110
+
111
+
112
+ // code is ported from https://github.com/sinshu/meltysynth/ to work with js. I'm too dumb to understand the math behind this...
113
+ let w = 2 * Math.PI * voice.filter.cutoffHz / sampleRate; // we're in the audioworkletglobalscope so we can use sampleRate
114
+ let cosw = Math.cos(w);
115
+ let alpha = Math.sin(w) / (2 * voice.filter.reasonanceGain);
116
+
117
+ let b1 = (1 - cosw) * qGain;
118
+ let b0 = b1 / 2;
119
+ let b2 = b0;
120
+ let a0 = 1 + alpha;
121
+ let a1 = -2 * cosw;
122
+ let a2 = 1 - alpha;
123
+
124
+ // set coefficients
125
+ voice.filter.a0 = b0 / a0;
126
+ voice.filter.a1 = b1 / a0;
127
+ voice.filter.a2 = b2 / a0;
128
+ voice.filter.a3 = a1 / a0;
129
+ voice.filter.a4 = a2 / a0;
130
+ }
@@ -0,0 +1,73 @@
1
+ import { timecentsToSeconds } from './unit_converter.js'
2
+ import { generatorTypes } from '../../../soundfont/read/generators.js'
3
+ import { getModulatorCurveValue } from './modulator_curves.js'
4
+ import { modulatorCurveTypes } from '../../../soundfont/read/modulators.js'
5
+
6
+ /**
7
+ * modulation_envelope.js
8
+ * purpose: calculates the modulation envelope for the given voice
9
+ */
10
+ const PEAK = 1;
11
+
12
+ // 1000 should be precise enough
13
+ const CONVEX_ATTACK = new Float32Array(1000);
14
+ for (let i = 0; i < CONVEX_ATTACK.length; i++) {
15
+ // this makes the db linear ( i think
16
+ CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
17
+ }
18
+
19
+ /**
20
+ * Calculates the current modulation envelope value for the given time and voice
21
+ * @param voice {WorkletVoice} the voice we're working on
22
+ * @param currentTime {number} in seconds
23
+ * @returns {number} modenv value, from 0 to 1
24
+ */
25
+ export function getModEnvValue(voice, currentTime)
26
+ {
27
+ // calculate env times
28
+ let attack = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
29
+ let decay = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]));
30
+ let hold = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]));
31
+
32
+ // calculate absolute times
33
+ if(voice.isInRelease && voice.releaseStartTime < currentTime)
34
+ {
35
+ let release = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
36
+ if(voice.modulatedGenerators[generatorTypes.releaseModEnv] < -7199)
37
+ {
38
+ // prevent lowpass bugs if release is instant
39
+ return voice.releaseStartModEnv;
40
+ }
41
+ return (1 - (currentTime - voice.releaseStartTime) / release) * voice.releaseStartModEnv;
42
+ }
43
+
44
+ let sustain = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
45
+ let delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]) + voice.startTime;
46
+ let attackEnd = attack + delayEnd;
47
+ let holdEnd = hold + attackEnd;
48
+ let decayEnd = decay + holdEnd;
49
+
50
+ let modEnvVal
51
+ if(currentTime < delayEnd)
52
+ {
53
+ modEnvVal = 0; // delay
54
+ }
55
+ else if(currentTime < attackEnd)
56
+ {
57
+ modEnvVal = CONVEX_ATTACK[~~((1 - (attackEnd - currentTime) / attack) * 1000)]; // convex attack
58
+ }
59
+ else if(currentTime < holdEnd)
60
+ {
61
+ modEnvVal = PEAK; // peak
62
+ }
63
+ else if(currentTime < decayEnd)
64
+ {
65
+ modEnvVal = (1 - (decayEnd - currentTime) / decay) * (sustain - PEAK) + PEAK; // decay
66
+ }
67
+ else
68
+ {
69
+ modEnvVal = sustain; // sustain
70
+ }
71
+ voice.currentModEnvValue = modEnvVal;
72
+ return modEnvVal;
73
+ }
@@ -0,0 +1,86 @@
1
+ import { modulatorCurveTypes } from '../../../soundfont/read/modulators.js'
2
+
3
+ /**
4
+ * modulator_curves.js
5
+ * precomputes modulator concave and conves curves and calculates a curve value for a given polarity, direction and type
6
+ */
7
+
8
+ // the length of the precomputed curve tables
9
+ export const MOD_PRECOMPUTED_LENGTH = 16384;
10
+
11
+ // Precalculate lookup tables for concave and convers
12
+ const concave = new Float32Array(MOD_PRECOMPUTED_LENGTH);
13
+ const convex = new Float32Array(MOD_PRECOMPUTED_LENGTH);
14
+ // the equation is taken from FluidSynth as it's the standard for soundFonts
15
+ // more precisely, this:
16
+ // https://github.com/FluidSynth/fluidsynth/blob/cb8da1e1e2c0a5cff2bab6a419755b598b793384/src/gentables/gen_conv.c#L55
17
+ concave[0] = 0;
18
+ concave[MOD_PRECOMPUTED_LENGTH - 1] = 1;
19
+
20
+ convex[0] = 0;
21
+ convex[MOD_PRECOMPUTED_LENGTH - 1] = 1;
22
+ for(let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
23
+ {
24
+ let x = (-200 * 2 / 960) * Math.log(i / (MOD_PRECOMPUTED_LENGTH - 1)) / Math.LN10;
25
+ convex[i] = 1 - x;
26
+ concave[MOD_PRECOMPUTED_LENGTH - 1 - i] = x;
27
+ }
28
+
29
+ /**
30
+ * Transforms a value with a given curve type
31
+ * @param polarity {number} 0 or 1
32
+ * @param direction {number} 0 or 1
33
+ * @param curveType {number} see modulatorCurveTypes in modulators.js
34
+ * @param value {number} the linear value, 0 to 1
35
+ * @returns {number} the transformed value, 0 to 1 or -1 to 1
36
+ */
37
+ export function getModulatorCurveValue(direction, curveType, value, polarity) {
38
+ // inverse the value if needed
39
+ if(direction)
40
+ {
41
+ value = 1 - value
42
+ }
43
+ switch (curveType) {
44
+ case modulatorCurveTypes.linear:
45
+ if (polarity) {
46
+ // bipolar
47
+ return value * 2 - 1;
48
+ }
49
+ return value;
50
+
51
+ case modulatorCurveTypes.switch:
52
+ // switch
53
+ value = value > 0.5 ? 1 : 0;
54
+ if (polarity) {
55
+ // multiply
56
+ return value * 2 - 1;
57
+ }
58
+ return value;
59
+
60
+ case modulatorCurveTypes.concave:
61
+ // look up the value
62
+ if(polarity)
63
+ {
64
+ value = value * 2 - 1;
65
+ if(value < 0)
66
+ {
67
+ return 1 - concave[~~(value * -MOD_PRECOMPUTED_LENGTH)] - 1;
68
+ }
69
+ return concave[~~value * MOD_PRECOMPUTED_LENGTH];
70
+ }
71
+ return concave[~~(value * MOD_PRECOMPUTED_LENGTH)]
72
+
73
+ case modulatorCurveTypes.convex:
74
+ // look up the value
75
+ if(polarity)
76
+ {
77
+ value = value * 2 - 1;
78
+ if(value < 0)
79
+ {
80
+ return 1 - convex[~~(value * -MOD_PRECOMPUTED_LENGTH)] - 1;
81
+ }
82
+ return convex[~~(value * MOD_PRECOMPUTED_LENGTH)];
83
+ }
84
+ return convex[~~(value * MOD_PRECOMPUTED_LENGTH)];
85
+ }
86
+ }
@@ -0,0 +1,81 @@
1
+ export const WORKLET_SYSTEM_REVERB_DIVIDER = 500;
2
+ export const WORKLET_SYSTEM_CHORUS_DIVIDER = 500;
3
+ /**
4
+ * stereo_panner.js
5
+ * purpose: pans a given voice out to the stereo output and to the effects' outputs
6
+ */
7
+
8
+ /**
9
+ * Pans the voice to the given output buffers
10
+ * @param gainLeft {number} the left channel gain
11
+ * @param gainRight {number} the right channel gain
12
+ * @param inputBuffer {Float32Array} the input buffer in mono
13
+ * @param outputLeft {Float32Array} left output buffer
14
+ * @param outputRight {Float32Array} right output buffer
15
+ * @param reverb {Float32Array[]} stereo reverb input
16
+ * @param reverbLevel {number} 0 to 1000, the level of reverb to send
17
+ * @param chorus {Float32Array[]} stereo chorus buttfer
18
+ * @param chorusLevel {number} 0 to 1000, the level of chorus to send
19
+ */
20
+ export function panVoice(gainLeft,
21
+ gainRight,
22
+ inputBuffer,
23
+ outputLeft, outputRight,
24
+ reverb,
25
+ reverbLevel,
26
+ chorus,
27
+ chorusLevel)
28
+ {
29
+ if(isNaN(inputBuffer[0]))
30
+ {
31
+ return;
32
+ }
33
+
34
+ if(reverbLevel > 0)
35
+ {
36
+ const reverbLeft = reverb[0];
37
+ const reverbRight = reverb[1];
38
+ // cap reverb
39
+ reverbLevel = Math.min(reverbLevel, 1000);
40
+ const reverbGain = reverbLevel / WORKLET_SYSTEM_REVERB_DIVIDER;
41
+ const reverbLeftGain = gainLeft * reverbGain;
42
+ const reverbRightGain = gainRight * reverbGain;
43
+ for (let i = 0; i < inputBuffer.length; i++)
44
+ {
45
+ reverbLeft[i] += reverbLeftGain * inputBuffer[i];
46
+ reverbRight[i] += reverbRightGain * inputBuffer[i];
47
+ }
48
+ }
49
+
50
+ if(chorusLevel > 0)
51
+ {
52
+ const chorusLeft = chorus[0];
53
+ const chorusRight = chorus[1];
54
+ // cap chorus
55
+ chorusLevel = Math.min(chorusLevel, 1000);
56
+ const chorusGain = chorusLevel / WORKLET_SYSTEM_CHORUS_DIVIDER;
57
+ const chorusLeftGain = gainLeft * chorusGain;
58
+ const chorusRightGain = gainRight * chorusGain;
59
+ for (let i = 0; i < inputBuffer.length; i++)
60
+ {
61
+ chorusLeft[i] += chorusLeftGain * inputBuffer[i];
62
+ chorusRight[i] += chorusRightGain * inputBuffer[i];
63
+ }
64
+ }
65
+
66
+ // mix out the audio data
67
+ if(gainLeft > 0)
68
+ {
69
+ for (let i = 0; i < inputBuffer.length; i++)
70
+ {
71
+ outputLeft[i] += gainLeft * inputBuffer[i];
72
+ }
73
+ }
74
+ if(gainRight > 0)
75
+ {
76
+ for (let i = 0; i < inputBuffer.length; i++)
77
+ {
78
+ outputRight[i] += gainRight * inputBuffer[i];
79
+ }
80
+ }
81
+ }