spessasynth_core 1.1.3 → 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 -313
  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 -223
  177. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -133
  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 -272
  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 -175
  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 -285
  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
@@ -0,0 +1,401 @@
1
+ import { decibelAttenuationToGain, timecentsToSeconds } from "./unit_converter.js";
2
+
3
+ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
4
+
5
+ /**
6
+ * volume_envelope.js
7
+ * purpose: applies a volume envelope for a given voice
8
+ */
9
+
10
+ export const VOLUME_ENVELOPE_SMOOTHING_FACTOR = 0.01;
11
+
12
+ const DB_SILENCE = 100;
13
+ const PERCEIVED_DB_SILENCE = 90;
14
+ // around 96 dB of attenuation
15
+ const PERCEIVED_GAIN_SILENCE = 0.000015; // can't go lower than that (see #50)
16
+
17
+ /**
18
+ * VOL ENV STATES:
19
+ * 0 - delay
20
+ * 1 - attack
21
+ * 2 - hold/peak
22
+ * 3 - decay
23
+ * 4 - sustain
24
+ * release indicates by isInRelease property
25
+ */
26
+
27
+ export class VolumeEnvelope
28
+ {
29
+ /**
30
+ * The envelope's current time in samples
31
+ * @type {number}
32
+ */
33
+ currentSampleTime = 0;
34
+ /**
35
+ * The sample rate in Hz
36
+ * @type {number}
37
+ */
38
+ sampleRate;
39
+ /**
40
+ * The current attenuation of the envelope in dB
41
+ * @type {number}
42
+ */
43
+ currentAttenuationDb = DB_SILENCE;
44
+ /**
45
+ * The current stage of the volume envelope
46
+ * @type {0|1|2|3|4}
47
+ */
48
+ state = 0;
49
+ /**
50
+ * The dB attenuation of the envelope when it entered the release stage
51
+ * @type {number}
52
+ */
53
+ releaseStartDb = DB_SILENCE;
54
+ /**
55
+ * The time in samples relative to the start of the envelope
56
+ * @type {number}
57
+ */
58
+ releaseStartTimeSamples = 0;
59
+ /**
60
+ * The current gain applied to the voice in the release stage
61
+ * @type {number}
62
+ */
63
+ currentReleaseGain = 1;
64
+ /**
65
+ * The attack duration in samples
66
+ * @type {number}
67
+ */
68
+ attackDuration = 0;
69
+ /**
70
+ * The decay duration in samples
71
+ * @type {number}
72
+ */
73
+ decayDuration = 0;
74
+ /**
75
+ * The release duration in samples
76
+ * @type {number}
77
+ */
78
+ releaseDuration = 0;
79
+ /**
80
+ * The voice's absolute attenuation as linear gain
81
+ * @type {number}
82
+ */
83
+ attenuation = 0;
84
+ /**
85
+ * The attenuation target, which the "attenuation" property is linearly interpolated towards (gain)
86
+ * @type {number}
87
+ */
88
+ attenuationTargetGain = 0;
89
+ /**
90
+ * The attenuation target, which the "attenuation" property is linearly interpolated towards (dB)
91
+ * @type {number}
92
+ */
93
+ attenuationTarget = 0;
94
+ /**
95
+ * The voice's sustain amount in dB, relative to attenuation
96
+ * @type {number}
97
+ */
98
+ sustainDbRelative = 0;
99
+ /**
100
+ * The time in samples to the end of delay stage, relative to the start of the envelope
101
+ * @type {number}
102
+ */
103
+ delayEnd = 0;
104
+ /**
105
+ * The time in samples to the end of attack stage, relative to the start of the envelope
106
+ * @type {number}
107
+ */
108
+ attackEnd = 0;
109
+ /**
110
+ * The time in samples to the end of hold stage, relative to the start of the envelope
111
+ * @type {number}
112
+ */
113
+ holdEnd = 0;
114
+ /**
115
+ * The time in samples to the end of decay stage, relative to the start of the envelope
116
+ * @type {number}
117
+ */
118
+ decayEnd = 0;
119
+
120
+ /**
121
+ * @param sampleRate {number} Hz
122
+ * @param initialDecay {number} cb
123
+ */
124
+ constructor(sampleRate, initialDecay)
125
+ {
126
+ this.sampleRate = sampleRate;
127
+ /**
128
+ * if sustain stge is silent,
129
+ * then we can turn off the voice when it is silent.
130
+ * We can't do that with modulated as it can silence the volume and then raise it again, and the voice must keep playing
131
+ * @type {boolean}
132
+ */
133
+ this.canEndOnSilentSustain = initialDecay / 10 >= PERCEIVED_DB_SILENCE;
134
+ }
135
+
136
+ /**
137
+ * Starts the release phase in the envelope
138
+ * @param voice {Voice} the voice this envelope belongs to
139
+ */
140
+ static startRelease(voice)
141
+ {
142
+ voice.volumeEnvelope.releaseStartTimeSamples = voice.volumeEnvelope.currentSampleTime;
143
+ voice.volumeEnvelope.currentReleaseGain = decibelAttenuationToGain(voice.volumeEnvelope.currentAttenuationDb);
144
+ VolumeEnvelope.recalculate(voice);
145
+ }
146
+
147
+ /**
148
+ * Recalculates the envelope
149
+ * @param voice {Voice} the voice this envelope belongs to
150
+ */
151
+ static recalculate(voice)
152
+ {
153
+ const env = voice.volumeEnvelope;
154
+ const timecentsToSamples = tc =>
155
+ {
156
+ return Math.max(0, Math.floor(timecentsToSeconds(tc) * env.sampleRate));
157
+ };
158
+ // calculate absolute times (they can change so we have to recalculate every time
159
+ env.attenuationTarget = Math.max(
160
+ 0,
161
+ Math.min(voice.modulatedGenerators[generatorTypes.initialAttenuation], 1440)
162
+ ) / 10; // divide by ten to get decibels
163
+ env.attenuationTargetGain = decibelAttenuationToGain(env.attenuationTarget);
164
+ env.sustainDbRelative = Math.min(DB_SILENCE, voice.modulatedGenerators[generatorTypes.sustainVolEnv] / 10);
165
+ const sustainDb = Math.min(DB_SILENCE, env.sustainDbRelative);
166
+
167
+ // calculate durations
168
+ env.attackDuration = timecentsToSamples(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
169
+
170
+ // decay: sfspec page 35: the time is for change from attenuation to -100dB,
171
+ // therefore, we need to calculate the real time
172
+ // (changing from attenuation to sustain instead of -100dB)
173
+ const fullChange = voice.modulatedGenerators[generatorTypes.decayVolEnv];
174
+ const keyNumAddition = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay];
175
+ const fraction = sustainDb / DB_SILENCE;
176
+ env.decayDuration = timecentsToSamples(fullChange + keyNumAddition) * fraction;
177
+
178
+ env.releaseDuration = timecentsToSamples(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
179
+
180
+ // calculate absolute end times for the values
181
+ env.delayEnd = timecentsToSamples(voice.modulatedGenerators[generatorTypes.delayVolEnv]);
182
+ env.attackEnd = env.attackDuration + env.delayEnd;
183
+
184
+ // make sure to take keyNumToVolEnvHold into account!
185
+ const holdExcursion = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvHold];
186
+ env.holdEnd = timecentsToSamples(voice.modulatedGenerators[generatorTypes.holdVolEnv]
187
+ + holdExcursion)
188
+ + env.attackEnd;
189
+
190
+ env.decayEnd = env.decayDuration + env.holdEnd;
191
+
192
+ // if this is the first recalculation and the voice has no attack or delay time, set current db to peak
193
+ if (env.state === 0 && env.attackEnd === 0)
194
+ {
195
+ // env.currentAttenuationDb = env.attenuationTarget;
196
+ env.state = 2;
197
+ }
198
+
199
+ // check if voice is in release
200
+ if (voice.isInRelease)
201
+ {
202
+ // no interpolation this time: force update to actual attenuation and calculate release start from there
203
+ //env.attenuation = Math.min(DB_SILENCE, env.attenuationTarget);
204
+ const sustainDb = Math.max(0, Math.min(DB_SILENCE, env.sustainDbRelative));
205
+ const fraction = sustainDb / DB_SILENCE;
206
+ env.decayDuration = timecentsToSamples(fullChange + keyNumAddition) * fraction;
207
+
208
+ switch (env.state)
209
+ {
210
+ case 0:
211
+ env.releaseStartDb = DB_SILENCE;
212
+ break;
213
+
214
+ case 1:
215
+ // attack phase: get linear gain of the attack phase when release started
216
+ // and turn it into db as we're ramping the db up linearly
217
+ // (to make volume go down exponentially)
218
+ // attack is linear (in gain) so we need to do get db from that
219
+ let elapsed = 1 - ((env.attackEnd - env.releaseStartTimeSamples) / env.attackDuration);
220
+ // calculate the gain that the attack would have, so
221
+ // turn that into db
222
+ env.releaseStartDb = 20 * Math.log10(elapsed) * -1;
223
+ break;
224
+
225
+ case 2:
226
+ env.releaseStartDb = 0;
227
+ break;
228
+
229
+ case 3:
230
+ env.releaseStartDb = (1 - (env.decayEnd - env.releaseStartTimeSamples) / env.decayDuration) * sustainDb;
231
+ break;
232
+
233
+ case 4:
234
+ env.releaseStartDb = sustainDb;
235
+ break;
236
+ }
237
+ env.releaseStartDb = Math.max(0, Math.min(env.releaseStartDb, DB_SILENCE));
238
+ if (env.releaseStartDb >= PERCEIVED_DB_SILENCE)
239
+ {
240
+ voice.finished = true;
241
+ }
242
+ env.currentReleaseGain = decibelAttenuationToGain(env.releaseStartDb);
243
+
244
+ // release: sfspec page 35: the time is for change from attenuation to -100dB,
245
+ // therefore, we need to calculate the real time
246
+ // (changing from release start to -100dB instead of from peak to -100dB)
247
+ const releaseFraction = (DB_SILENCE - env.releaseStartDb) / DB_SILENCE;
248
+ env.releaseDuration *= releaseFraction;
249
+
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Applies volume envelope gain to the given output buffer
255
+ * @param voice {Voice} the voice we're working on
256
+ * @param audioBuffer {Float32Array} the audio buffer to modify
257
+ * @param centibelOffset {number} the centibel offset of volume, for modLFOtoVolume
258
+ * @param smoothingFactor {number} the adjusted smoothing factor for the envelope
259
+ * @description essentially we use approach of 100dB is silence, 0dB is peak, and always add attenuation to that (which is interpolated)
260
+ */
261
+ static apply(voice, audioBuffer, centibelOffset, smoothingFactor)
262
+ {
263
+ const env = voice.volumeEnvelope;
264
+ let decibelOffset = centibelOffset / 10;
265
+
266
+ const attenuationSmoothing = smoothingFactor;
267
+
268
+ // RELEASE PHASE
269
+ if (voice.isInRelease)
270
+ {
271
+ let elapsedRelease = env.currentSampleTime - env.releaseStartTimeSamples;
272
+ if (elapsedRelease >= env.releaseDuration)
273
+ {
274
+ for (let i = 0; i < audioBuffer.length; i++)
275
+ {
276
+ audioBuffer[i] = 0;
277
+ }
278
+ voice.finished = true;
279
+ return;
280
+ }
281
+ let dbDifference = DB_SILENCE - env.releaseStartDb;
282
+ for (let i = 0; i < audioBuffer.length; i++)
283
+ {
284
+ // attenuation interpolation
285
+ env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
286
+ let db = (elapsedRelease / env.releaseDuration) * dbDifference + env.releaseStartDb;
287
+ env.currentReleaseGain = env.attenuation * decibelAttenuationToGain(db + decibelOffset);
288
+ audioBuffer[i] *= env.currentReleaseGain;
289
+ env.currentSampleTime++;
290
+ elapsedRelease++;
291
+ }
292
+
293
+ if (env.currentReleaseGain <= PERCEIVED_GAIN_SILENCE)
294
+ {
295
+ voice.finished = true;
296
+ }
297
+ return;
298
+ }
299
+
300
+ let filledBuffer = 0;
301
+ switch (env.state)
302
+ {
303
+ case 0:
304
+ // delay phase, no sound is produced
305
+ while (env.currentSampleTime < env.delayEnd)
306
+ {
307
+ env.currentAttenuationDb = DB_SILENCE;
308
+ audioBuffer[filledBuffer] = 0;
309
+
310
+ env.currentSampleTime++;
311
+ if (++filledBuffer >= audioBuffer.length)
312
+ {
313
+ return;
314
+ }
315
+ }
316
+ env.state++;
317
+ // fallthrough
318
+
319
+ case 1:
320
+ // attack phase: ramp from 0 to attenuation
321
+ while (env.currentSampleTime < env.attackEnd)
322
+ {
323
+ // attenuation interpolation
324
+ env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
325
+
326
+ // Special case: linear gain ramp instead of linear db ramp
327
+ let linearAttenuation = 1 - (env.attackEnd - env.currentSampleTime) / env.attackDuration; // 0 to 1
328
+ audioBuffer[filledBuffer] *= linearAttenuation * env.attenuation * decibelAttenuationToGain(
329
+ decibelOffset);
330
+ // set current attenuation to peak as its invalid during this phase
331
+ env.currentAttenuationDb = 0;
332
+
333
+ env.currentSampleTime++;
334
+ if (++filledBuffer >= audioBuffer.length)
335
+ {
336
+ return;
337
+ }
338
+ }
339
+ env.state++;
340
+ // fallthrough
341
+
342
+ case 2:
343
+ // hold/peak phase: stay at attenuation
344
+ while (env.currentSampleTime < env.holdEnd)
345
+ {
346
+ // attenuation interpolation
347
+ env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
348
+
349
+ audioBuffer[filledBuffer] *= env.attenuation * decibelAttenuationToGain(decibelOffset);
350
+ env.currentAttenuationDb = 0;
351
+
352
+ env.currentSampleTime++;
353
+ if (++filledBuffer >= audioBuffer.length)
354
+ {
355
+ return;
356
+ }
357
+ }
358
+ env.state++;
359
+ // fallthrough
360
+
361
+ case 3:
362
+ // decay phase: linear ramp from attenuation to sustain
363
+ while (env.currentSampleTime < env.decayEnd)
364
+ {
365
+ // attenuation interpolation
366
+ env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
367
+
368
+ env.currentAttenuationDb = (1 - (env.decayEnd - env.currentSampleTime) / env.decayDuration) * env.sustainDbRelative;
369
+ audioBuffer[filledBuffer] *= env.attenuation * decibelAttenuationToGain(env.currentAttenuationDb + decibelOffset);
370
+
371
+ env.currentSampleTime++;
372
+ if (++filledBuffer >= audioBuffer.length)
373
+ {
374
+ return;
375
+ }
376
+ }
377
+ env.state++;
378
+ // fallthrough
379
+
380
+ case 4:
381
+ if (env.canEndOnSilentSustain && env.sustainDbRelative >= PERCEIVED_DB_SILENCE)
382
+ {
383
+ voice.finished = true;
384
+ }
385
+ // sustain phase: stay at sustain
386
+ while (true)
387
+ {
388
+ // attenuation interpolation
389
+ env.attenuation += (env.attenuationTargetGain - env.attenuation) * attenuationSmoothing;
390
+
391
+ audioBuffer[filledBuffer] *= env.attenuation * decibelAttenuationToGain(env.sustainDbRelative + decibelOffset);
392
+ env.currentAttenuationDb = env.sustainDbRelative;
393
+ env.currentSampleTime++;
394
+ if (++filledBuffer >= audioBuffer.length)
395
+ {
396
+ return;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * wavetable_oscillator.js
3
+ * purpose: plays back raw audio data at an arbitrary playback rate
4
+ */
5
+
6
+ /**
7
+ *
8
+ * @enum {number}
9
+ */
10
+ export const interpolationTypes = {
11
+ linear: 0,
12
+ nearestNeighbor: 1,
13
+ fourthOrder: 2
14
+ };
15
+
16
+
17
+ export class WavetableOscillator
18
+ {
19
+
20
+ /**
21
+ * Fills the output buffer with raw sample data using linear interpolation
22
+ * @param voice {Voice} the voice we're working on
23
+ * @param outputBuffer {Float32Array} the output buffer to write to
24
+ */
25
+ static getSampleLinear(voice, outputBuffer)
26
+ {
27
+ const sample = voice.sample;
28
+ let cur = sample.cursor;
29
+ const sampleData = sample.sampleData;
30
+
31
+ if (sample.isLooping)
32
+ {
33
+ const loopLength = sample.loopEnd - sample.loopStart;
34
+ for (let i = 0; i < outputBuffer.length; i++)
35
+ {
36
+ // check for loop
37
+ while (cur >= sample.loopEnd)
38
+ {
39
+ cur -= loopLength;
40
+ }
41
+
42
+ // grab the 2 nearest points
43
+ const floor = ~~cur;
44
+ let ceil = floor + 1;
45
+
46
+ while (ceil >= sample.loopEnd)
47
+ {
48
+ ceil -= loopLength;
49
+ }
50
+
51
+ const fraction = cur - floor;
52
+
53
+ // grab the samples and interpolate
54
+ const upper = sampleData[ceil];
55
+ const lower = sampleData[floor];
56
+ outputBuffer[i] = (lower + (upper - lower) * fraction);
57
+
58
+ cur += sample.playbackStep * voice.currentTuningCalculated;
59
+ }
60
+ }
61
+ else
62
+ {
63
+ if (sample.loopingMode === 2 && !voice.isInRelease)
64
+ {
65
+ return;
66
+ }
67
+ for (let i = 0; i < outputBuffer.length; i++)
68
+ {
69
+
70
+ // linear interpolation
71
+ const floor = ~~cur;
72
+ const ceil = floor + 1;
73
+
74
+ // flag the voice as finished if needed
75
+ if (ceil >= sample.end)
76
+ {
77
+ voice.finished = true;
78
+ return;
79
+ }
80
+
81
+ const fraction = cur - floor;
82
+
83
+ // grab the samples and interpolate
84
+ const upper = sampleData[ceil];
85
+ const lower = sampleData[floor];
86
+ outputBuffer[i] = (lower + (upper - lower) * fraction);
87
+
88
+ cur += sample.playbackStep * voice.currentTuningCalculated;
89
+ }
90
+ }
91
+ voice.sample.cursor = cur;
92
+ }
93
+
94
+ /**
95
+ * Fills the output buffer with raw sample data using no interpolation (nearest neighbor)
96
+ * @param voice {Voice} the voice we're working on
97
+ * @param outputBuffer {Float32Array} the output buffer to write to
98
+ */
99
+ static getSampleNearest(voice, outputBuffer)
100
+ {
101
+ const sample = voice.sample;
102
+ let cur = sample.cursor;
103
+ const loopLength = sample.loopEnd - sample.loopStart;
104
+ const sampleData = sample.sampleData;
105
+ if (voice.sample.isLooping)
106
+ {
107
+ for (let i = 0; i < outputBuffer.length; i++)
108
+ {
109
+ // check for loop
110
+ while (cur >= sample.loopEnd)
111
+ {
112
+ cur -= loopLength;
113
+ }
114
+
115
+ // grab the nearest neighbor
116
+ let ceil = ~~cur + 1;
117
+
118
+ while (ceil >= sample.loopEnd)
119
+ {
120
+ ceil -= loopLength;
121
+ }
122
+
123
+ outputBuffer[i] = sampleData[ceil];
124
+ cur += sample.playbackStep * voice.currentTuningCalculated;
125
+ }
126
+ }
127
+ else
128
+ {
129
+ if (sample.loopingMode === 2 && !voice.isInRelease)
130
+ {
131
+ return;
132
+ }
133
+ for (let i = 0; i < outputBuffer.length; i++)
134
+ {
135
+
136
+ // nearest neighbor
137
+ const ceil = ~~cur + 1;
138
+
139
+ // flag the voice as finished if needed
140
+ if (ceil >= sample.end)
141
+ {
142
+ voice.finished = true;
143
+ return;
144
+ }
145
+
146
+ //nearest neighbor (uncomment to use)
147
+ outputBuffer[i] = sampleData[ceil];
148
+ cur += sample.playbackStep * voice.currentTuningCalculated;
149
+ }
150
+ }
151
+ sample.cursor = cur;
152
+ }
153
+
154
+
155
+ /**
156
+ * Fills the output buffer with raw sample data using cubic interpolation
157
+ * @param voice {Voice} the voice we're working on
158
+ * @param outputBuffer {Float32Array} the output buffer to write to
159
+ */
160
+ static getSampleCubic(voice, outputBuffer)
161
+ {
162
+ const sample = voice.sample;
163
+ let cur = sample.cursor;
164
+ const sampleData = sample.sampleData;
165
+
166
+ if (sample.isLooping)
167
+ {
168
+ const loopLength = sample.loopEnd - sample.loopStart;
169
+ for (let i = 0; i < outputBuffer.length; i++)
170
+ {
171
+ // check for loop
172
+ while (cur >= sample.loopEnd)
173
+ {
174
+ cur -= loopLength;
175
+ }
176
+
177
+ // math comes from
178
+ // https://stackoverflow.com/questions/1125666/how-do-you-do-bicubic-or-other-non-linear-interpolation-of-re-sampled-audio-da
179
+
180
+ // grab the 4 points
181
+ const y0 = ~~cur; // point before the cursor. twice bitwise not is just a faster Math.floor
182
+ let y1 = y0 + 1; // point after the cursor
183
+ let y2 = y1 + 1; // point 1 after the cursor
184
+ let y3 = y2 + 1; // point 2 after the cursor
185
+ const t = cur - y0; // distance from y0 to cursor
186
+ // y0 is not handled here
187
+ // as it's math.floor of cur which is handled above
188
+ if (y1 >= sample.loopEnd)
189
+ {
190
+ y1 -= loopLength;
191
+ }
192
+ if (y2 >= sample.loopEnd)
193
+ {
194
+ y2 -= loopLength;
195
+ }
196
+ if (y3 >= sample.loopEnd)
197
+ {
198
+ y3 -= loopLength;
199
+ }
200
+
201
+ // grab the samples
202
+ const x0 = sampleData[y0];
203
+ const x1 = sampleData[y1];
204
+ const x2 = sampleData[y2];
205
+ const x3 = sampleData[y3];
206
+
207
+ // interpolate
208
+ // const c0 = x1
209
+ const c1 = 0.5 * (x2 - x0);
210
+ const c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3);
211
+ const c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2));
212
+ outputBuffer[i] = (((((c3 * t) + c2) * t) + c1) * t) + x1;
213
+
214
+
215
+ cur += sample.playbackStep * voice.currentTuningCalculated;
216
+ }
217
+ }
218
+ else
219
+ {
220
+ if (sample.loopingMode === 2 && !voice.isInRelease)
221
+ {
222
+ return;
223
+ }
224
+ for (let i = 0; i < outputBuffer.length; i++)
225
+ {
226
+
227
+ // math comes from
228
+ // https://stackoverflow.com/questions/1125666/how-do-you-do-bicubic-or-other-non-linear-interpolation-of-re-sampled-audio-da
229
+
230
+ // grab the 4 points
231
+ const y0 = ~~cur; // point before the cursor. twice bitwise not is just a faster Math.floor
232
+ let y1 = y0 + 1; // point after the cursor
233
+ let y2 = y1 + 1; // point 1 after the cursor
234
+ let y3 = y2 + 1; // point 2 after the cursor
235
+ const t = cur - y0; // distance from y0 to cursor
236
+
237
+ // flag as finished if needed
238
+ if (y1 >= sample.end ||
239
+ y2 >= sample.end ||
240
+ y3 >= sample.end)
241
+ {
242
+ voice.finished = true;
243
+ return;
244
+ }
245
+
246
+ // grab the samples
247
+ const x0 = sampleData[y0];
248
+ const x1 = sampleData[y1];
249
+ const x2 = sampleData[y2];
250
+ const x3 = sampleData[y3];
251
+
252
+ // interpolate
253
+ const c1 = 0.5 * (x2 - x0);
254
+ const c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3);
255
+ const c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2));
256
+ outputBuffer[i] = (((((c3 * t) + c2) * t) + c1) * t) + x1;
257
+
258
+ cur += sample.playbackStep * voice.currentTuningCalculated;
259
+ }
260
+ }
261
+ voice.sample.cursor = cur;
262
+ }
263
+ }