spessasynth_core 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/LICENSE +3 -26
  2. package/README.md +156 -474
  3. package/index.js +73 -8
  4. package/package.json +21 -8
  5. package/src/externals/fflate/LICENSE +21 -0
  6. package/src/externals/fflate/fflate.min.js +1 -0
  7. package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  8. package/src/externals/stbvorbis_sync/LICENSE +202 -0
  9. package/src/externals/stbvorbis_sync/NOTICE +6 -0
  10. package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  11. package/src/midi/README.md +32 -0
  12. package/src/midi/basic_midi.js +567 -0
  13. package/src/midi/midi_builder.js +202 -0
  14. package/src/midi/midi_loader.js +324 -0
  15. package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
  16. package/src/midi/midi_sequence.js +224 -0
  17. package/src/midi/midi_tools/get_note_times.js +154 -0
  18. package/src/midi/midi_tools/midi_editor.js +611 -0
  19. package/src/midi/midi_tools/midi_writer.js +99 -0
  20. package/src/midi/midi_tools/rmidi_writer.js +567 -0
  21. package/src/midi/midi_tools/used_keys_loaded.js +238 -0
  22. package/src/midi/xmf_loader.js +454 -0
  23. package/src/sequencer/README.md +5 -0
  24. package/src/sequencer/events.js +81 -0
  25. package/src/sequencer/play.js +349 -0
  26. package/src/sequencer/process_event.js +165 -0
  27. package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
  28. package/src/sequencer/sequencer_engine.js +367 -0
  29. package/src/sequencer/song_control.js +201 -0
  30. package/src/soundfont/README.md +13 -0
  31. package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
  32. package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
  33. package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
  34. package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
  35. package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
  36. package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
  37. package/src/soundfont/basic_soundfont/generator.js +220 -0
  38. package/src/soundfont/basic_soundfont/modulator.js +378 -0
  39. package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
  40. package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
  41. package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
  42. package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
  43. package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
  44. package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
  45. package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
  46. package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
  47. package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
  48. package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
  49. package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
  50. package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
  51. package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
  52. package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
  53. package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
  54. package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
  55. package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
  56. package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
  57. package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
  58. package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
  59. package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
  60. package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
  61. package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
  62. package/src/soundfont/dls/articulator_converter.js +396 -0
  63. package/src/soundfont/dls/dls_destinations.js +38 -0
  64. package/src/soundfont/dls/dls_preset.js +44 -0
  65. package/src/soundfont/dls/dls_sample.js +75 -0
  66. package/src/soundfont/dls/dls_soundfont.js +186 -0
  67. package/src/soundfont/dls/dls_sources.js +62 -0
  68. package/src/soundfont/dls/dls_zone.js +95 -0
  69. package/src/soundfont/dls/read_articulation.js +299 -0
  70. package/src/soundfont/dls/read_instrument.js +121 -0
  71. package/src/soundfont/dls/read_instrument_list.js +17 -0
  72. package/src/soundfont/dls/read_lart.js +35 -0
  73. package/src/soundfont/dls/read_region.js +152 -0
  74. package/src/soundfont/dls/read_samples.js +270 -0
  75. package/src/soundfont/load_soundfont.js +21 -0
  76. package/src/soundfont/read_sf2/generators.js +46 -0
  77. package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
  78. package/src/soundfont/read_sf2/modulators.js +36 -0
  79. package/src/soundfont/read_sf2/presets.js +80 -0
  80. package/src/soundfont/read_sf2/samples.js +304 -0
  81. package/src/soundfont/read_sf2/soundfont.js +305 -0
  82. package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
  83. package/src/synthetizer/README.md +7 -0
  84. package/src/synthetizer/audio_engine/README.md +9 -0
  85. package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
  86. package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
  87. package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
  88. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
  89. package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
  90. package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
  91. package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
  92. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
  93. package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
  94. package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
  95. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
  96. package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
  97. package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
  98. package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
  99. package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
  100. package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
  101. package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
  102. package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
  103. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
  104. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
  105. package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
  106. package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
  107. package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
  108. package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
  109. package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
  110. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
  111. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
  112. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
  113. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
  114. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
  115. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
  116. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
  117. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
  118. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
  119. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
  120. package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
  121. package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
  122. package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
  123. package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
  124. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
  125. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
  126. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
  127. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
  128. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
  129. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
  130. package/src/synthetizer/audio_engine/main_processor.js +804 -0
  131. package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
  132. package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
  133. package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
  134. package/src/synthetizer/synth_constants.js +22 -0
  135. package/{spessasynth_core → src}/utils/README.md +1 -0
  136. package/src/utils/buffer_to_wav.js +185 -0
  137. package/src/utils/byte_functions/big_endian.js +32 -0
  138. package/src/utils/byte_functions/little_endian.js +77 -0
  139. package/src/utils/byte_functions/string.js +107 -0
  140. package/src/utils/byte_functions/variable_length_quantity.js +42 -0
  141. package/src/utils/fill_with_defaults.js +21 -0
  142. package/src/utils/indexed_array.js +52 -0
  143. package/{spessasynth_core → src}/utils/loggin.js +70 -78
  144. package/src/utils/other.js +92 -0
  145. package/src/utils/sysex_detector.js +58 -0
  146. package/src/utils/xg_hacks.js +193 -0
  147. package/.idea/inspectionProfiles/Project_Default.xml +0 -10
  148. package/.idea/jsLibraryMappings.xml +0 -6
  149. package/.idea/modules.xml +0 -8
  150. package/.idea/spessasynth_core.iml +0 -12
  151. package/.idea/vcs.xml +0 -6
  152. package/spessasynth_core/midi_parser/README.md +0 -3
  153. package/spessasynth_core/midi_parser/midi_loader.js +0 -386
  154. package/spessasynth_core/sequencer/sequencer.js +0 -202
  155. package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
  156. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
  157. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
  158. package/spessasynth_core/soundfont/README.md +0 -4
  159. package/spessasynth_core/soundfont/chunk/generators.js +0 -205
  160. package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
  161. package/spessasynth_core/soundfont/chunk/presets.js +0 -264
  162. package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
  163. package/spessasynth_core/soundfont/chunk/samples.js +0 -250
  164. package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
  165. package/spessasynth_core/synthetizer/README.md +0 -6
  166. package/spessasynth_core/synthetizer/synthesizer.js +0 -303
  167. package/spessasynth_core/synthetizer/worklet_system/README.md +0 -3
  168. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -290
  169. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -280
  170. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +0 -102
  171. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +0 -77
  172. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +0 -140
  173. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -266
  174. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -104
  175. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
  176. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +0 -222
  177. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -95
  178. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +0 -73
  179. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -76
  180. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -194
  181. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -83
  182. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -173
  183. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +0 -106
  184. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +0 -313
  185. package/spessasynth_core/utils/buffer_to_wav.js +0 -70
  186. package/spessasynth_core/utils/byte_functions.js +0 -141
  187. package/spessasynth_core/utils/other.js +0 -49
  188. package/spessasynth_core/utils/shiftable_array.js +0 -26
  189. package/spessasynth_core/utils/stbvorbis_sync.js +0 -1877
@@ -1,222 +0,0 @@
1
- import { generatorTypes } from '../../../soundfont/chunk/generators.js'
2
- import { absCentsToHz, 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 } from '../worklet_utilities/volume_envelope.js'
9
- import { applyLowpassFilter } from '../worklet_utilities/lowpass_filter.js'
10
- import { MIN_NOTE_LENGTH } from '../../synthesizer.js'
11
-
12
- /**
13
- * Renders a voice to the stereo output buffer
14
- * @param channel {WorkletProcessorChannel} the voice's channel
15
- * @param voice {WorkletVoice} the voice to render
16
- * @param output {Float32Array[]} the output buffer
17
- * @param reverbOutput {Float32Array[]} output for reverb
18
- * @param chorusOutput {Float32Array[]} output for chorus
19
- * @this {Synthesizer}
20
- */
21
- export function renderVoice(channel, voice, output, reverbOutput, chorusOutput)
22
- {
23
- // if no matching sample, perhaps it's still being loaded...?
24
- if(this.workletDumpedSamplesList[voice.sample.sampleID] === undefined)
25
- {
26
- return;
27
- }
28
-
29
- // check if release
30
- if(!voice.isInRelease) {
31
- // if not in release, check if the release time is
32
- if (this.currentTime >= voice.releaseStartTime) {
33
- voice.releaseStartModEnv = voice.currentModEnvValue;
34
- voice.isInRelease = true;
35
- }
36
- }
37
-
38
-
39
- // if the initial attenuation is more than 100dB, skip the voice (it's silent anyway)
40
- if(voice.modulatedGenerators[generatorTypes.initialAttenuation] > 2500)
41
- {
42
- if(voice.isInRelease)
43
- {
44
- voice.finished = true;
45
- }
46
- return;
47
- }
48
-
49
- // TUNING
50
-
51
- // calculate tuning
52
- let cents = voice.modulatedGenerators[generatorTypes.fineTune]
53
- + channel.customControllers[customControllers.channelTuning]
54
- + channel.customControllers[customControllers.channelTranspose]
55
- + channel.customControllers[customControllers.masterTuning];
56
- let semitones = voice.modulatedGenerators[generatorTypes.coarseTune];
57
-
58
- // calculate tuning by key
59
- cents += (voice.targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
60
-
61
- // vibrato LFO
62
- const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
63
- if(vibratoDepth > 0)
64
- {
65
- const vibStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVibLFO]);
66
- const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
67
- const lfoVal = getLFOValue(vibStart, vibFreqHz, this.currentTime);
68
- if(lfoVal)
69
- {
70
- cents += lfoVal * (vibratoDepth * channel.customControllers[customControllers.modulationMultiplier]);
71
- }
72
- }
73
-
74
- // lowpass frequency
75
- let lowpassCents = voice.modulatedGenerators[generatorTypes.initialFilterFc];
76
-
77
- // mod LFO
78
- const modPitchDepth = voice.modulatedGenerators[generatorTypes.modLfoToPitch];
79
- const modVolDepth = voice.modulatedGenerators[generatorTypes.modLfoToVolume];
80
- const modFilterDepth = voice.modulatedGenerators[generatorTypes.modLfoToFilterFc];
81
- let modLfoCentibels = 0;
82
- if(modPitchDepth + modFilterDepth + modVolDepth > 0)
83
- {
84
- const modStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModLFO]);
85
- const modFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqModLFO]);
86
- const modLfoValue = getLFOValue(modStart, modFreqHz, this.currentTime);
87
- cents += modLfoValue * (modPitchDepth * channel.customControllers[customControllers.modulationMultiplier]);
88
- modLfoCentibels = modLfoValue * modVolDepth;
89
- lowpassCents += modLfoValue * modFilterDepth;
90
- }
91
-
92
- // channel vibrato (GS NRPN)
93
- if(channel.channelVibrato.depth > 0)
94
- {
95
- const channelVibrato = getLFOValue(voice.startTime + channel.channelVibrato.delay, channel.channelVibrato.rate, this.currentTime);
96
- if(channelVibrato)
97
- {
98
- cents += channelVibrato * channel.channelVibrato.depth;
99
- }
100
- }
101
-
102
- // mod env
103
- const modEnvPitchDepth = voice.modulatedGenerators[generatorTypes.modEnvToPitch];
104
- const modEnvFilterDepth = voice.modulatedGenerators[generatorTypes.modEnvToFilterFc];
105
- const modEnv = getModEnvValue(voice, this.currentTime);
106
- lowpassCents += modEnv * modEnvFilterDepth;
107
- cents += modEnv * modEnvPitchDepth;
108
-
109
- // finally calculate the playback rate
110
- const centsTotal = ~~(cents + semitones * 100);
111
- if(centsTotal !== voice.currentTuningCents)
112
- {
113
- voice.currentTuningCents = centsTotal;
114
- voice.currentTuningCalculated = Math.pow(2, centsTotal / 1200);
115
- }
116
-
117
- // PANNING
118
- const pan = ( (Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan] )) + 500) / 1000) ; // 0 to 1
119
-
120
- // SYNTHESIS
121
- const bufferOut = new Float32Array(output[0].length);
122
-
123
- // wavetable oscillator
124
- getOscillatorData(voice, this.workletDumpedSamplesList[voice.sample.sampleID], bufferOut);
125
-
126
-
127
- // lowpass filter
128
- applyLowpassFilter(voice, bufferOut, lowpassCents, this.sampleRate);
129
-
130
- // volenv
131
- applyVolumeEnvelope(voice, bufferOut, this.currentTime, modLfoCentibels, this.sampleTime);
132
-
133
- // pan the voice and write out
134
- voice.currentPan += (pan - voice.currentPan) * 0.1; // smooth out pan to prevent clicking
135
- const panLeft = (1 - voice.currentPan) * this.panLeft;
136
- const panRight = voice.currentPan * this.panRight;
137
- panVoice(
138
- panLeft,
139
- panRight,
140
- bufferOut,
141
- output,
142
- reverbOutput, voice.modulatedGenerators[generatorTypes.reverbEffectsSend],
143
- chorusOutput, voice.modulatedGenerators[generatorTypes.chorusEffectsSend]);
144
- }
145
-
146
- /**
147
- * @param channel {WorkletProcessorChannel}
148
- * @param voice {WorkletVoice}
149
- * @return {number}
150
- */
151
- function getPriority(channel, voice)
152
- {
153
- let priority = 0;
154
- if(channel.drumChannel)
155
- {
156
- // important
157
- priority += 5;
158
- }
159
- if(voice.isInRelease)
160
- {
161
- // not important
162
- priority -= 5;
163
- }
164
- // less velocity = less important
165
- priority += voice.velocity / 25; // map to 0-5
166
- // the newer, more important
167
- priority -= voice.volumeEnvelopeState;
168
- if(voice.isInRelease)
169
- {
170
- priority -= 5;
171
- }
172
- priority -= voice.currentAttenuationDb / 50;
173
- return priority;
174
- }
175
-
176
- /**
177
- * @this {Synthesizer}
178
- * @param amount {number}
179
- */
180
- export function voiceKilling(amount)
181
- {
182
- let allVoices = [];
183
- for (const channel of this.workletProcessorChannels)
184
- {
185
- for (const voice of channel.voices)
186
- {
187
- if (!voice.finished)
188
- {
189
- const priority = getPriority(channel, voice);
190
- allVoices.push({ channel, voice, priority });
191
- }
192
- }
193
- }
194
-
195
- // Step 2: Sort voices by priority (ascending order)
196
- allVoices.sort((a, b) => a.priority - b.priority);
197
- const voicesToRemove = allVoices.slice(0, amount);
198
-
199
- for (const { channel, voice } of voicesToRemove)
200
- {
201
- const index = channel.voices.indexOf(voice);
202
- if (index > -1)
203
- {
204
- channel.voices.splice(index, 1);
205
- }
206
- }
207
- }
208
-
209
- /**
210
- * Stops the voice
211
- * @param voice {WorkletVoice} the voice to stop
212
- * @this {Synthesizer}
213
- */
214
- export function releaseVoice(voice)
215
- {
216
- voice.releaseStartTime = this.currentTime;
217
- // check if the note is shorter than the min note time, if so, extend it
218
- if(voice.releaseStartTime - voice.startTime < MIN_NOTE_LENGTH)
219
- {
220
- voice.releaseStartTime = voice.startTime + MIN_NOTE_LENGTH;
221
- }
222
- }
@@ -1,95 +0,0 @@
1
- import { generatorTypes } from '../../../soundfont/chunk/generators.js'
2
- import { absCentsToHz, decibelAttenuationToGain } from './unit_converter.js'
3
-
4
- /**
5
- * lowpass_filter.js
6
- * purpose: applies a low pass filter to a voice
7
- * note to self: most of this is code is just javascript version of the C code from fluidsynth,
8
- * they are the real smart guys.
9
- * Shoutout to them!
10
- */
11
-
12
-
13
- /**
14
- * Applies a low-pass filter to the given buffer
15
- * @param voice {WorkletVoice} the voice we're working on
16
- * @param outputBuffer {Float32Array} the buffer to apply the filter to
17
- * @param cutoffCents {number} cutoff frequency in cents
18
- * @param sampleRate {number} in hertz
19
- * @this {Synthesizer}
20
- */
21
- export function applyLowpassFilter(voice, outputBuffer, cutoffCents, sampleRate)
22
- {
23
- if(cutoffCents > 13499)
24
- {
25
- return; // filter is open
26
- }
27
-
28
- // check if the frequency has changed. if so, calculate new coefficients
29
- if(voice.filter.cutoffCents !== cutoffCents || voice.filter.reasonanceCb !== voice.modulatedGenerators[generatorTypes.initialFilterQ])
30
- {
31
- voice.filter.cutoffCents = cutoffCents;
32
- voice.filter.reasonanceCb = voice.modulatedGenerators[generatorTypes.initialFilterQ];
33
- calculateCoefficients(voice, sampleRate);
34
- }
35
-
36
- // filter the input
37
- for (let i = 0; i < outputBuffer.length; i++) {
38
- let input = outputBuffer[i];
39
- let filtered = voice.filter.a0 * input
40
- + voice.filter.a1 * voice.filter.x1
41
- + voice.filter.a2 * voice.filter.x2
42
- - voice.filter.a3 * voice.filter.y1
43
- - voice.filter.a4 * voice.filter.y2;
44
-
45
- // set buffer
46
- voice.filter.x2 = voice.filter.x1;
47
- voice.filter.x1 = input;
48
- voice.filter.y2 = voice.filter.y1;
49
- voice.filter.y1 = filtered;
50
-
51
- outputBuffer[i] = filtered;
52
- }
53
- }
54
-
55
- /**
56
- * @param voice {WorkletVoice}
57
- * @param sampleRate {number}
58
- */
59
- function calculateCoefficients(voice, sampleRate)
60
- {
61
- voice.filter.cutoffHz = absCentsToHz(voice.filter.cutoffCents);
62
-
63
- // fix cutoff on low frequencies (fluid_iir_filter.c line 392)
64
- if(voice.filter.cutoffHz > 0.45 * sampleRate)
65
- {
66
- voice.filter.cutoffHz = 0.45 * sampleRate;
67
- }
68
-
69
- // adjust the filterQ (fluid_iir_filter.c line 204)
70
- const qDb = (voice.filter.reasonanceCb / 10) - 3.01;
71
- voice.filter.reasonanceGain = decibelAttenuationToGain(-1 * qDb); // -1 because it's attenuation, and we don't want attenuation
72
-
73
- // reduce the gain by the Q factor (fluid_iir_filter.c line 250)
74
- const qGain = 1 / Math.sqrt(voice.filter.reasonanceGain);
75
-
76
-
77
- // code is ported from https://github.com/sinshu/meltysynth/ to work with js. I'm too dumb to understand the math behind this...
78
- let w = 2 * Math.PI * voice.filter.cutoffHz / sampleRate;
79
- let cosw = Math.cos(w);
80
- let alpha = Math.sin(w) / (2 * voice.filter.reasonanceGain);
81
-
82
- let b1 = (1 - cosw) * qGain;
83
- let b0 = b1 / 2;
84
- let b2 = b0;
85
- let a0 = 1 + alpha;
86
- let a1 = -2 * cosw;
87
- let a2 = 1 - alpha;
88
-
89
- // set coefficients
90
- voice.filter.a0 = b0 / a0;
91
- voice.filter.a1 = b1 / a0;
92
- voice.filter.a2 = b2 / a0;
93
- voice.filter.a3 = a1 / a0;
94
- voice.filter.a4 = a2 / a0;
95
- }
@@ -1,73 +0,0 @@
1
- import { timecentsToSeconds } from './unit_converter.js'
2
- import { generatorTypes } from '../../../soundfont/chunk/generators.js'
3
- import { getModulatorCurveValue } from './modulator_curves.js'
4
- import { modulatorCurveTypes } from '../../../soundfont/chunk/modulators.js'
5
-
6
- /**
7
- * modulation_envelope.js
8
- * purpose: calculates the modulation envelope for the given voice
9
- */
10
- const PEAK = 1;
11
-
12
- // 1000 should be precise enough
13
- const CONVEX_ATTACK = new Float32Array(1000);
14
- for (let i = 0; i < CONVEX_ATTACK.length; i++) {
15
- // this makes the db linear ( i think
16
- CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
17
- }
18
-
19
- /**
20
- * Calculates the current modulation envelope value for the given time and voice
21
- * @param voice {WorkletVoice} the voice we're working on
22
- * @param currentTime {number} in seconds
23
- * @returns {number} modenv value, from 0 to 1
24
- */
25
- export function getModEnvValue(voice, currentTime)
26
- {
27
- // calculate env times
28
- let attack = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
29
- let decay = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]));
30
- let hold = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]));
31
-
32
- // calculate absolute times
33
- if(voice.isInRelease && voice.releaseStartTime < currentTime)
34
- {
35
- let release = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
36
- if(voice.modulatedGenerators[generatorTypes.releaseModEnv] < -7199)
37
- {
38
- // prevent lowpass bugs if release is instant
39
- return voice.releaseStartModEnv;
40
- }
41
- return (1 - (currentTime - voice.releaseStartTime) / release) * voice.releaseStartModEnv;
42
- }
43
-
44
- let sustain = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
45
- let delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]) + voice.startTime;
46
- let attackEnd = attack + delayEnd;
47
- let holdEnd = hold + attackEnd;
48
- let decayEnd = decay + holdEnd;
49
-
50
- let modEnvVal
51
- if(currentTime < delayEnd)
52
- {
53
- modEnvVal = 0; // delay
54
- }
55
- else if(currentTime < attackEnd)
56
- {
57
- modEnvVal = CONVEX_ATTACK[~~((1 - (attackEnd - currentTime) / attack) * 1000)]; // convex attack
58
- }
59
- else if(currentTime < holdEnd)
60
- {
61
- modEnvVal = PEAK; // peak
62
- }
63
- else if(currentTime < decayEnd)
64
- {
65
- modEnvVal = (1 - (decayEnd - currentTime) / decay) * (sustain - PEAK) + PEAK; // decay
66
- }
67
- else
68
- {
69
- modEnvVal = sustain; // sustain
70
- }
71
- voice.currentModEnvValue = modEnvVal;
72
- return modEnvVal;
73
- }
@@ -1,76 +0,0 @@
1
- export const WORKLET_SYSTEM_REVERB_DIVIDER = 200;
2
- export const WORKLET_SYSTEM_CHORUS_DIVIDER = 500;
3
- /**
4
- * stereo_panner.js
5
- * purpose: pans a given voice out to the stereo output and to the effects' outputs
6
- */
7
-
8
- /**
9
- * Pans the voice to the given output buffers
10
- * @param gainLeft {number} the left channel gain
11
- * @param gainRight {number} the right channel gain
12
- * @param inputBuffer {Float32Array} the input buffer in mono
13
- * @param output {Float32Array[]} stereo output buffer
14
- * @param reverb {Float32Array[]} stereo reverb input
15
- * @param reverbLevel {number} 0 to 1000, the level of reverb to send
16
- * @param chorus {Float32Array[]} stereo chorus buttfer
17
- * @param chorusLevel {number} 0 to 1000, the level of chorus to send
18
- */
19
- export function panVoice(gainLeft,
20
- gainRight,
21
- inputBuffer,
22
- output,
23
- reverb,
24
- reverbLevel,
25
- chorus,
26
- chorusLevel)
27
- {
28
- if(isNaN(inputBuffer[0]))
29
- {
30
- return;
31
- }
32
-
33
- if(reverbLevel > 0 && reverb !== undefined)
34
- {
35
- const reverbLeft = reverb[0];
36
- const reverbRight = reverb[1];
37
- // cap reverb
38
- reverbLevel = Math.min(reverbLevel, 1000);
39
- const reverbGain = reverbLevel / WORKLET_SYSTEM_REVERB_DIVIDER;
40
- const reverbLeftGain = gainLeft * reverbGain;
41
- const reverbRightGain = gainRight * reverbGain;
42
- for (let i = 0; i < inputBuffer.length; i++) {
43
- reverbLeft[i] += reverbLeftGain * inputBuffer[i];
44
- reverbRight[i] += reverbRightGain * inputBuffer[i];
45
- }
46
- }
47
-
48
- if(chorusLevel > 0 && chorus !== undefined)
49
- {
50
- const chorusLeft = chorus[0];
51
- const chorusRight = chorus[1];
52
- // cap chorus
53
- chorusLevel = Math.min(chorusLevel, 1000);
54
- const chorusGain = chorusLevel / WORKLET_SYSTEM_CHORUS_DIVIDER;
55
- const chorusLeftGain = gainLeft * chorusGain;
56
- const chorusRightGain = gainRight * chorusGain;
57
- for (let i = 0; i < inputBuffer.length; i++) {
58
- chorusLeft[i] += chorusLeftGain * inputBuffer[i];
59
- chorusRight[i] += chorusRightGain * inputBuffer[i];
60
- }
61
- }
62
-
63
- const leftChannel = output[0];
64
- const rightChannel = output[1];
65
- if(gainLeft > 0)
66
- {
67
- for (let i = 0; i < inputBuffer.length; i++) {
68
- leftChannel[i] += gainLeft * inputBuffer[i];
69
- }
70
- }
71
- if(gainRight > 0) {
72
- for (let i = 0; i < inputBuffer.length; i++) {
73
- rightChannel[i] += gainRight * inputBuffer[i];
74
- }
75
- }
76
- }
@@ -1,194 +0,0 @@
1
- import { decibelAttenuationToGain, timecentsToSeconds } from './unit_converter.js'
2
- import { generatorTypes } from '../../../soundfont/chunk/generators.js'
3
-
4
- /**
5
- * volume_envelope.js
6
- * purpose: applies a volume envelope for a given voice
7
- */
8
-
9
- const DB_SILENCE = 100;
10
- const GAIN_SILENCE = 0.005;
11
-
12
- /**
13
- * VOL ENV STATES:
14
- * 0 - delay
15
- * 1 - attack
16
- * 2 - hold/peak
17
- * 3 - decay
18
- * 4 - sustain
19
- * release is indicated by isInRelease property
20
- */
21
-
22
- /**
23
- * Applies volume envelope gain to the given output buffer
24
- * @param voice {WorkletVoice} the voice we're working on
25
- * @param audioBuffer {Float32Array} the audio buffer to modify
26
- * @param currentTime {number} the current audio time
27
- * @param centibelOffset {number} the centibel offset of volume, for modLFOtoVolume
28
- * @param sampleTime {number} single sample time in seconds, usually 1 / 44100 of a second
29
- */
30
-
31
- export function applyVolumeEnvelope(voice, audioBuffer, currentTime, centibelOffset, sampleTime)
32
- {
33
- // calculate values
34
- let decibelOffset = centibelOffset / 10;
35
-
36
- // calculate env times
37
- let attack = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
38
- let decay = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayVolEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay]));
39
-
40
- // calculate absolute times
41
- let attenuation = voice.modulatedGenerators[generatorTypes.initialAttenuation] / 10; // divide by ten to get decibelts
42
- let release = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
43
- let sustain = attenuation + voice.modulatedGenerators[generatorTypes.sustainVolEnv] / 10;
44
- let delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVolEnv]) + voice.startTime;
45
- let attackEnd = attack + delayEnd;
46
- let holdEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdVolEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvHold])) + attackEnd;
47
- let decayEnd = decay + holdEnd;
48
-
49
- // check if voice is in release
50
- if(voice.isInRelease)
51
- {
52
- // calculate the db attenuation at the time of release (not a constant because it can change (ex, volume set to 0, the sound should cut off)
53
- let releaseStartDb;
54
- switch (voice.volumeEnvelopeState)
55
- {
56
- case 0:
57
- // no sound: fill with zero and skip!
58
- for (let i = 0; i < audioBuffer.length; i++) {
59
- audioBuffer[i] = 0;
60
- }
61
- return;
62
-
63
- case 1:
64
- // attack phase
65
- // attack is linear (in gain) so we need to do get db from that
66
- let elapsed = 1 - ((attackEnd - voice.releaseStartTime) / attack);
67
- // calculate the gain that the attack would have
68
- let attackGain = elapsed * decibelAttenuationToGain(attenuation + decibelOffset);
69
-
70
- // turn that into db
71
- releaseStartDb = 20 * Math.log10(attackGain) * -1;
72
- break;
73
-
74
- case 2:
75
- // hold
76
- releaseStartDb = attenuation;
77
- break;
78
-
79
- case 3:
80
- // decay
81
- releaseStartDb = (1 - (decayEnd - voice.releaseStartTime) / decay) * (sustain - attenuation) + attenuation;
82
- break;
83
-
84
- case 4:
85
- // sustain
86
- releaseStartDb = sustain;
87
- break;
88
-
89
- }
90
-
91
- // uncommented because doesn't seem to be needed but just in case
92
- // // if the voice is not released, but state set to true (due to min note length, simply use the release db)
93
- // if(voice.releaseStartTime > currentTime)
94
- // {
95
- // const gain = decibelAttenuationToGain(releaseStartDb + decibelOffset);
96
- // for (let i = 0; i < audioBuffer.length; i++) {
97
- // audioBuffer[i] = gain * audioBuffer[i];
98
- // }
99
- // return;
100
- // }
101
-
102
- let elapsedRelease = currentTime - voice.releaseStartTime;
103
- let dbDifference = DB_SILENCE - releaseStartDb;
104
- let gain;
105
- for (let i = 0; i < audioBuffer.length; i++) {
106
- let db = (elapsedRelease / release) * dbDifference + releaseStartDb;
107
- gain = decibelAttenuationToGain(db + decibelOffset);
108
- audioBuffer[i] = gain * audioBuffer[i];
109
- elapsedRelease += sampleTime;
110
- }
111
-
112
- if(gain <= GAIN_SILENCE)
113
- {
114
- voice.finished = true;
115
- }
116
- return;
117
- }
118
- let currentFrameTime = currentTime;
119
- let dbAttenuation;
120
- for (let i = 0; i < audioBuffer.length; i++) {
121
- switch(voice.volumeEnvelopeState)
122
- {
123
- case 0:
124
- // delay phase, no sound is produced
125
- if(currentFrameTime >= delayEnd)
126
- {
127
- voice.volumeEnvelopeState++;
128
- }
129
- else
130
- {
131
- dbAttenuation = DB_SILENCE;
132
- audioBuffer[i] = 0;
133
-
134
- // no need to go through the hassle of converting. Skip
135
- currentFrameTime += sampleTime;
136
- continue;
137
- }
138
- // fallthrough
139
-
140
- case 1:
141
- // attack phase: ramp from 0 to attenuation
142
- if(currentFrameTime >= attackEnd)
143
- {
144
- voice.volumeEnvelopeState++;
145
- }
146
- else {
147
- // Special case: linear gain ramp instead of linear db ramp
148
- const elapsed = (attackEnd - currentFrameTime) / attack;
149
- dbAttenuation = 10 * Math.log10((elapsed * (attenuation - DB_SILENCE) + DB_SILENCE) * -1);
150
- audioBuffer[i] = audioBuffer[i] * (1 - elapsed) * decibelAttenuationToGain(attenuation + decibelOffset);
151
- currentFrameTime += sampleTime;
152
- continue
153
-
154
- }
155
- // fallthrough
156
-
157
- case 2:
158
- // hold/peak phase: stay at attenuation
159
- if(currentFrameTime >= holdEnd)
160
- {
161
- voice.volumeEnvelopeState++;
162
- }
163
- else
164
- {
165
- dbAttenuation = attenuation;
166
- break;
167
- }
168
- // fallthrough
169
-
170
- case 3:
171
- // decay phase: linear ramp from attenuation to sustain
172
- if(currentFrameTime >= decayEnd)
173
- {
174
- voice.volumeEnvelopeState++;
175
- }
176
- else
177
- {
178
- dbAttenuation = (1 - (decayEnd - currentFrameTime) / decay) * (sustain - attenuation) + attenuation;
179
- break
180
- }
181
- // fallthrough
182
-
183
- case 4:
184
- // sustain phase: stay at sustain
185
- dbAttenuation = sustain;
186
-
187
- }
188
- // apply gain and advance the time
189
- const gain = decibelAttenuationToGain(dbAttenuation + decibelOffset);
190
- audioBuffer[i] = audioBuffer[i] * gain;
191
- currentFrameTime += sampleTime;
192
- }
193
- voice.currentAttenuationDb = dbAttenuation;
194
- }