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,467 @@
1
+ import {
2
+ CONTROLLER_TABLE_SIZE,
3
+ CUSTOM_CONTROLLER_TABLE_SIZE,
4
+ customControllers,
5
+ dataEntryStates,
6
+ NON_CC_INDEX_OFFSET
7
+ } from "./controller_tables.js";
8
+ import {
9
+ resetControllers,
10
+ resetControllersRP15Compliant,
11
+ resetParameters
12
+ } from "../engine_methods/controller_control/reset_controllers.js";
13
+ import { renderVoice } from "../engine_methods/render_voice.js";
14
+ import { panVoice } from "./stereo_panner.js";
15
+ import { killNote } from "../engine_methods/stopping_notes/kill_note.js";
16
+ import { setTuning } from "../engine_methods/tuning_control/set_tuning.js";
17
+ import { setModulationDepth } from "../engine_methods/tuning_control/set_modulation_depth.js";
18
+ import { dataEntryFine } from "../engine_methods/data_entry/data_entry_fine.js";
19
+ import { controllerChange } from "../engine_methods/controller_control/controller_change.js";
20
+ import { stopAllNotes } from "../engine_methods/stopping_notes/stop_all_notes.js";
21
+ import { muteChannel } from "../engine_methods/mute_channel.js";
22
+ import { transposeChannel } from "../engine_methods/tuning_control/transpose_channel.js";
23
+ import { dataEntryCoarse } from "../engine_methods/data_entry/data_entry_coarse.js";
24
+ import { noteOn } from "../engine_methods/note_on.js";
25
+ import { noteOff } from "../engine_methods/stopping_notes/note_off.js";
26
+ import { polyPressure } from "../engine_methods/tuning_control/poly_pressure.js";
27
+ import { channelPressure } from "../engine_methods/tuning_control/channel_pressure.js";
28
+ import { pitchWheel } from "../engine_methods/tuning_control/pitch_wheel.js";
29
+ import { setOctaveTuning } from "../engine_methods/tuning_control/set_octave_tuning.js";
30
+ import { programChange } from "../engine_methods/program_change.js";
31
+ import { chooseBank, isSystemXG, parseBankSelect } from "../../../utils/xg_hacks.js";
32
+ import { DEFAULT_PERCUSSION } from "../../synth_constants.js";
33
+ import { modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
34
+
35
+ /**
36
+ * This class represents a single MIDI Channel within the synthesizer.
37
+ */
38
+ class MidiAudioChannel
39
+ {
40
+ /**
41
+ * An array of MIDI controller values and values used by modulators as the source (e.g., pitch bend, bend range, etc.).
42
+ * These are stored as 14-bit values.
43
+ * Refer to controller_tables.js for the index definitions.
44
+ * @type {Int16Array}
45
+ */
46
+ midiControllers = new Int16Array(CONTROLLER_TABLE_SIZE);
47
+
48
+ /**
49
+ * An array indicating if a controller, at the equivalent index in the midiControllers array, is locked
50
+ * (i.e., not allowed changing).
51
+ * A locked controller cannot be modified.
52
+ * @type {boolean[]}
53
+ */
54
+ lockedControllers = Array(CONTROLLER_TABLE_SIZE).fill(false);
55
+
56
+ /**
57
+ * An array of custom (non-SF2) control values such as RPN pitch tuning, transpose, modulation depth, etc.
58
+ * Refer to controller_tables.js for the index definitions.
59
+ * @type {Float32Array}
60
+ */
61
+ customControllers = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
62
+
63
+ /**
64
+ * The key shift of the channel (in semitones).
65
+ * @type {number}
66
+ */
67
+ channelTransposeKeyShift = 0;
68
+
69
+ /**
70
+ * An array of octave tuning values for each note on the channel.
71
+ * Each index corresponds to a note (0 = C, 1 = C#, ..., 11 = B).
72
+ * Note: Repeaded every 12 notes
73
+ * @type {Int8Array}
74
+ */
75
+ channelOctaveTuning = new Int8Array(128);
76
+
77
+ /**
78
+ * Will be updated every time something tuning-related gets changed.
79
+ * This is used to avoid a big addition for every voice rendering call.
80
+ * @type {number}
81
+ */
82
+ channelTuningCents = 0;
83
+
84
+ /**
85
+ * Indicates whether the sustain (hold) pedal is active.
86
+ * @type {boolean}
87
+ */
88
+ holdPedal = false;
89
+
90
+ /**
91
+ * Indicates whether this channel is a drum channel.
92
+ * @type {boolean}
93
+ */
94
+ drumChannel = false;
95
+
96
+ /**
97
+ * If greater than 0, overrides the velocity value for the channel, otherwise it's disabled.
98
+ * @type {number}
99
+ */
100
+ velocityOverride = 0;
101
+
102
+ /**
103
+ * Enables random panning for every note played on this channel.
104
+ * @type {boolean}
105
+ */
106
+ randomPan = false;
107
+
108
+ /**
109
+ * The current state of the data entry for the channel.
110
+ * @type {dataEntryStates}
111
+ */
112
+ dataEntryState = dataEntryStates.Idle;
113
+
114
+ /**
115
+ * The bank number of the channel (used for patch changes).
116
+ * @type {number}
117
+ */
118
+ bank = 0;
119
+
120
+ /**
121
+ * The bank number sent as channel properties.
122
+ * @type {number}
123
+ */
124
+ sentBank = 0;
125
+
126
+ /**
127
+ * The bank LSB number of the channel (used for patch changes in XG mode).
128
+ * @type {number}
129
+ */
130
+ bankLSB = 0;
131
+
132
+ /**
133
+ * The preset currently assigned to the channel.
134
+ * @type {BasicPreset}
135
+ */
136
+ preset = undefined;
137
+
138
+ /**
139
+ * Indicates whether the program on this channel is locked.
140
+ * @type {boolean}
141
+ */
142
+ lockPreset = false;
143
+
144
+ /**
145
+ * Indicates the MIDI system when the preset was locked.
146
+ * @type {SynthSystem}
147
+ */
148
+ lockedSystem = "gs";
149
+
150
+ /**
151
+ * Indicates whether the channel uses a preset from the override soundfont.
152
+ * @type {boolean}
153
+ */
154
+ presetUsesOverride = false;
155
+
156
+ /**
157
+ * Indicates whether the GS NRPN parameters are enabled for this channel.
158
+ * @type {boolean}
159
+ */
160
+ lockGSNRPNParams = false;
161
+
162
+ /**
163
+ * The vibrato settings for the channel.
164
+ * @type {Object}
165
+ * @property {number} depth - Depth of the vibrato effect in cents.
166
+ * @property {number} delay - Delay before the vibrato effect starts (in seconds).
167
+ * @property {number} rate - Rate of the vibrato oscillation (in Hz).
168
+ */
169
+ channelVibrato = { delay: 0, depth: 0, rate: 0 };
170
+
171
+ /**
172
+ * Indicates whether the channel is muted.
173
+ * @type {boolean}
174
+ */
175
+ isMuted = false;
176
+
177
+ /**
178
+ * An array of voices currently active on the channel.
179
+ * @type {Voice[]}
180
+ */
181
+ voices = [];
182
+
183
+ /**
184
+ * An array of voices that are sustained on the channel.
185
+ * @type {Voice[]}
186
+ */
187
+ sustainedVoices = [];
188
+
189
+ /**
190
+ * The channel's number (0-based index)
191
+ * @type {number}
192
+ */
193
+ channelNumber;
194
+
195
+ /**
196
+ * Parent processor instance.
197
+ * @type {SpessaSynthProcessor}
198
+ */
199
+ synth;
200
+
201
+ /**
202
+ * Constructs a new MIDI channel
203
+ * @param synth {SpessaSynthProcessor}
204
+ * @param preset {BasicPreset}
205
+ * @param channelNumber {number}
206
+ */
207
+ constructor(synth, preset, channelNumber)
208
+ {
209
+ this.synth = synth;
210
+ this.preset = preset;
211
+ this.channelNumber = channelNumber;
212
+ }
213
+
214
+ get isXGChannel()
215
+ {
216
+ return isSystemXG(this.synth.system) || (this.lockPreset && isSystemXG(this.lockedSystem));
217
+ }
218
+
219
+ /**
220
+ * @param type {customControllers|number}
221
+ * @param value {number}
222
+ */
223
+ setCustomController(type, value)
224
+ {
225
+ this.customControllers[type] = value;
226
+ this.updateChannelTuning();
227
+ }
228
+
229
+ updateChannelTuning()
230
+ {
231
+ this.channelTuningCents =
232
+ this.customControllers[customControllers.channelTuning] // RPN channel fine tuning
233
+ + this.customControllers[customControllers.channelTransposeFine] // user tuning (transpose)
234
+ + this.customControllers[customControllers.masterTuning] // master tuning, set by sysEx
235
+ + (this.customControllers[customControllers.channelTuningSemitones] * 100); // RPN channel coarse tuning
236
+ }
237
+
238
+ /**
239
+ * @param outputLeft {Float32Array} the left output buffer
240
+ * @param outputRight {Float32Array} the right output buffer
241
+ * @param reverbOutputLeft {Float32Array} left output for reverb
242
+ * @param reverbOutputRight {Float32Array} right output for reverb
243
+ * @param chorusOutputLeft {Float32Array} left output for chorus
244
+ * @param chorusOutputRight {Float32Array} right output for chorus
245
+ */
246
+ renderAudio(
247
+ outputLeft, outputRight,
248
+ reverbOutputLeft, reverbOutputRight,
249
+ chorusOutputLeft, chorusOutputRight
250
+ )
251
+ {
252
+ this.voices = this.voices.filter(v => !this.renderVoice(
253
+ v, this.synth.currentSynthTime,
254
+ outputLeft, outputRight,
255
+ reverbOutputLeft, reverbOutputRight,
256
+ chorusOutputLeft, chorusOutputRight
257
+ ));
258
+ }
259
+
260
+ /**
261
+ * @param locked {boolean}
262
+ */
263
+ setPresetLock(locked)
264
+ {
265
+ this.lockPreset = locked;
266
+ if (locked)
267
+ {
268
+ this.lockedSystem = this.synth.system;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * @param bank {number}
274
+ * @param isLSB {boolean}
275
+ */
276
+ setBankSelect(bank, isLSB = false)
277
+ {
278
+ if (this.lockPreset)
279
+ {
280
+ return;
281
+ }
282
+ if (isLSB)
283
+ {
284
+ this.bankLSB = bank;
285
+ }
286
+ else
287
+ {
288
+ this.bank = bank;
289
+ const bankLogic = parseBankSelect(
290
+ this.getBankSelect(),
291
+ bank,
292
+ this.synth.system,
293
+ false,
294
+ this.drumChannel,
295
+ this.channelNumber
296
+ );
297
+ switch (bankLogic.drumsStatus)
298
+ {
299
+ default:
300
+ case 0:
301
+ break;
302
+
303
+ case 1:
304
+ if (this.channelNumber % 16 === DEFAULT_PERCUSSION)
305
+ {
306
+ // cannot disable drums on channel 9
307
+ this.bank = 127;
308
+ }
309
+ break;
310
+
311
+ case 2:
312
+ this.setDrums(true);
313
+ break;
314
+ }
315
+ }
316
+ }
317
+
318
+ /**
319
+ * @returns {number}
320
+ */
321
+ getBankSelect()
322
+ {
323
+ return chooseBank(this.bank, this.bankLSB, this.drumChannel, this.isXGChannel);
324
+ }
325
+
326
+ /**
327
+ * Changes a preset of this channel
328
+ * @param preset {BasicPreset}
329
+ */
330
+ setPreset(preset)
331
+ {
332
+ if (this.lockPreset)
333
+ {
334
+ return;
335
+ }
336
+ delete this.preset;
337
+ this.preset = preset;
338
+ }
339
+
340
+ /**
341
+ * Sets drums on channel.
342
+ * @param isDrum {boolean}
343
+ */
344
+ setDrums(isDrum)
345
+ {
346
+ if (this.lockPreset)
347
+ {
348
+ return;
349
+ }
350
+ if (this.drumChannel === isDrum)
351
+ {
352
+ return;
353
+ }
354
+ if (isDrum)
355
+ {
356
+ // clear transpose
357
+ this.channelTransposeKeyShift = 0;
358
+ this.drumChannel = true;
359
+ }
360
+ else
361
+ {
362
+ this.drumChannel = false;
363
+ }
364
+ this.presetUsesOverride = false;
365
+ this.synth.callEvent("drumchange", {
366
+ channel: this.channelNumber,
367
+ isDrumChannel: this.drumChannel
368
+ });
369
+ this.programChange(this.preset.program);
370
+ this.sendChannelProperty();
371
+ }
372
+
373
+ /**
374
+ * Sets a custom vibrato
375
+ * @param depth {number} cents
376
+ * @param rate {number} Hz
377
+ * @param delay {number} seconds
378
+ */
379
+ setVibrato(depth, rate, delay)
380
+ {
381
+ if (this.lockGSNRPNParams)
382
+ {
383
+ return;
384
+ }
385
+ this.channelVibrato.rate = rate;
386
+ this.channelVibrato.delay = delay;
387
+ this.channelVibrato.depth = depth;
388
+ }
389
+
390
+ disableAndLockGSNRPN()
391
+ {
392
+ this.lockGSNRPNParams = true;
393
+ this.channelVibrato.rate = 0;
394
+ this.channelVibrato.delay = 0;
395
+ this.channelVibrato.depth = 0;
396
+ }
397
+
398
+
399
+ /**
400
+ * @typedef {Object} ChannelProperty
401
+ * @property {number} voicesAmount - the channel's current voice amount
402
+ * @property {number} pitchBend - the channel's current pitch bend from -8192 do 8192
403
+ * @property {number} pitchBendRangeSemitones - the pitch bend's range, in semitones
404
+ * @property {boolean} isMuted - indicates whether the channel is muted
405
+ * @property {boolean} isDrum - indicates whether the channel is a drum channel
406
+ * @property {number} transposition - the channel's transposition, in semitones
407
+ * @property {number} bank - the bank number of the current preset
408
+ * @property {number} program - the MIDI program number of the current preset
409
+ */
410
+
411
+
412
+ /**
413
+ * Sends this channel's property
414
+ */
415
+ sendChannelProperty()
416
+ {
417
+ if (!this.synth.enableEventSystem)
418
+ {
419
+ return;
420
+ }
421
+ /**
422
+ * @type {ChannelProperty}
423
+ */
424
+ const data = {
425
+ voicesAmount: this.voices.length,
426
+ pitchBend: this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel],
427
+ pitchBendRangeSemitones: this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] / 128,
428
+ isMuted: this.isMuted,
429
+ isDrum: this.drumChannel,
430
+ transposition: this.channelTransposeKeyShift + this.customControllers[customControllers.channelTransposeFine] / 100,
431
+ bank: this.sentBank,
432
+ program: this.preset.program
433
+ };
434
+ this.synth?.callbacks?.channelPropertyChange?.(data, this.channelNumber);
435
+ }
436
+ }
437
+
438
+ // voice
439
+ MidiAudioChannel.prototype.renderVoice = renderVoice;
440
+ MidiAudioChannel.prototype.panVoice = panVoice;
441
+ MidiAudioChannel.prototype.killNote = killNote;
442
+ MidiAudioChannel.prototype.stopAllNotes = stopAllNotes;
443
+ MidiAudioChannel.prototype.muteChannel = muteChannel;
444
+
445
+ // MIDI messages
446
+ MidiAudioChannel.prototype.noteOn = noteOn;
447
+ MidiAudioChannel.prototype.noteOff = noteOff;
448
+ MidiAudioChannel.prototype.polyPressure = polyPressure;
449
+ MidiAudioChannel.prototype.channelPressure = channelPressure;
450
+ MidiAudioChannel.prototype.pitchWheel = pitchWheel;
451
+ MidiAudioChannel.prototype.programChange = programChange;
452
+
453
+ // Tuning
454
+ MidiAudioChannel.prototype.setTuning = setTuning;
455
+ MidiAudioChannel.prototype.setOctaveTuning = setOctaveTuning;
456
+ MidiAudioChannel.prototype.setModulationDepth = setModulationDepth;
457
+ MidiAudioChannel.prototype.transposeChannel = transposeChannel;
458
+
459
+ // CC
460
+ MidiAudioChannel.prototype.controllerChange = controllerChange;
461
+ MidiAudioChannel.prototype.resetControllers = resetControllers;
462
+ MidiAudioChannel.prototype.resetControllersRP15Compliant = resetControllersRP15Compliant;
463
+ MidiAudioChannel.prototype.resetParameters = resetParameters;
464
+ MidiAudioChannel.prototype.dataEntryFine = dataEntryFine;
465
+ MidiAudioChannel.prototype.dataEntryCoarse = dataEntryCoarse;
466
+
467
+ export { MidiAudioChannel };
@@ -0,0 +1,181 @@
1
+ import { timecentsToSeconds } from "./unit_converter.js";
2
+ import { getModulatorCurveValue } from "./modulator_curves.js";
3
+ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
4
+ import { modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulator.js";
5
+
6
+ /**
7
+ * modulation_envelope.js
8
+ * purpose: calculates the modulation envelope for the given voice
9
+ */
10
+ const MODENV_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
+ {
16
+ // this makes the db linear (I think)
17
+ CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
18
+ }
19
+
20
+ export class ModulationEnvelope
21
+ {
22
+ /**
23
+ * The attack duration, in seconds
24
+ * @type {number}
25
+ */
26
+ attackDuration = 0;
27
+ /**
28
+ * The decay duration, in seconds
29
+ * @type {number}
30
+ */
31
+ decayDuration = 0;
32
+
33
+ /**
34
+ * The hold duration, in seconds
35
+ * @type {number}
36
+ */
37
+ holdDuration = 0;
38
+
39
+ /**
40
+ * Release duration, in seconds
41
+ * @type {number}
42
+ */
43
+ releaseDuration = 0;
44
+
45
+ /**
46
+ * The sustain level 0-1
47
+ * @type {number}
48
+ */
49
+ sustainLevel = 0;
50
+
51
+ /**
52
+ * Delay phase end time in seconds, absolute (audio context time)
53
+ * @type {number}
54
+ */
55
+ delayEnd = 0;
56
+ /**
57
+ * Attack phase end time in seconds, absolute (audio context time)
58
+ * @type {number}
59
+ */
60
+ attackEnd = 0;
61
+ /**
62
+ * Hold phase end time in seconds, absolute (audio context time)
63
+ * @type {number}
64
+ */
65
+ holdEnd = 0;
66
+ /**
67
+ * Decay phase end time in seconds, absolute (audio context time)
68
+ * @type {number}
69
+ */
70
+ decayEnd = 0;
71
+
72
+ /**
73
+ * The level of the envelope when the release phase starts
74
+ * @type {number}
75
+ */
76
+ releaseStartLevel = 0;
77
+
78
+ /**
79
+ * The current modulation envelope value
80
+ * @type {number}
81
+ */
82
+ currentValue = 0;
83
+
84
+ /**
85
+ * Starts the release phase in the envelope
86
+ * @param voice {Voice} the voice this envelope belongs to
87
+ */
88
+ static startRelease(voice)
89
+ {
90
+ ModulationEnvelope.recalculate(voice);
91
+ }
92
+
93
+ /**
94
+ * @param voice {Voice} the voice to recalculate
95
+ */
96
+ static recalculate(voice)
97
+ {
98
+ const env = voice.modulationEnvelope;
99
+
100
+ // in release? Might need to recalculate the value as it can be modulated
101
+ if (voice.isInRelease)
102
+ {
103
+ env.releaseStartLevel = ModulationEnvelope.getValue(voice, voice.releaseStartTime, true);
104
+ }
105
+
106
+ env.sustainLevel = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
107
+
108
+ env.attackDuration = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
109
+
110
+ const decayKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]);
111
+ const decayTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents);
112
+ // according to the specification, the decay time is the time it takes to reach 0% from 100%.
113
+ // calculate the time to reach actual sustain level,
114
+ // for example, sustain 0.6 will be 0.4 of the decay time
115
+ env.decayDuration = decayTime * (1 - env.sustainLevel);
116
+
117
+ const holdKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]);
118
+ env.holdDuration = timecentsToSeconds(holdKeyExcursionCents + voice.modulatedGenerators[generatorTypes.holdModEnv]);
119
+
120
+ const releaseTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
121
+ // release time is from the full level to 0%
122
+ // to get the actual time, multiply by the release start level
123
+ env.releaseDuration = releaseTime * env.releaseStartLevel;
124
+
125
+ env.delayEnd = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]);
126
+ env.attackEnd = env.delayEnd + env.attackDuration;
127
+ env.holdEnd = env.attackEnd + env.holdDuration;
128
+ env.decayEnd = env.holdEnd + env.decayDuration;
129
+ }
130
+
131
+ /**
132
+ * Calculates the current modulation envelope value for the given time and voice
133
+ * @param voice {Voice} the voice we are working on
134
+ * @param currentTime {number} in seconds
135
+ * @param ignoreRelease {boolean} if true, it will compute the value as if the voice was not released
136
+ * @returns {number} modenv value, from 0 to 1
137
+ */
138
+ static getValue(voice, currentTime, ignoreRelease = false)
139
+ {
140
+ const env = voice.modulationEnvelope;
141
+ if (voice.isInRelease && !ignoreRelease)
142
+ {
143
+ // if the voice is still in the delay phase,
144
+ // start level will be 0 that will result in divide by zero
145
+ if (env.releaseStartLevel === 0)
146
+ {
147
+ return 0;
148
+ }
149
+ return Math.max(
150
+ 0,
151
+ (1 - (currentTime - voice.releaseStartTime) / env.releaseDuration) * env.releaseStartLevel
152
+ );
153
+ }
154
+
155
+ if (currentTime < env.delayEnd)
156
+ {
157
+ env.currentValue = 0; // delay
158
+ }
159
+ else if (currentTime < env.attackEnd)
160
+ {
161
+ // modulation envelope uses convex curve for attack
162
+ env.currentValue = CONVEX_ATTACK[~~((1 - (env.attackEnd - currentTime) / env.attackDuration) * 1000)];
163
+ }
164
+ else if (currentTime < env.holdEnd)
165
+ {
166
+ // hold: stay at 1
167
+ env.currentValue = MODENV_PEAK;
168
+ }
169
+ else if (currentTime < env.decayEnd)
170
+ {
171
+ // decay: linear ramp from 1 to sustain level
172
+ env.currentValue = (1 - (env.decayEnd - currentTime) / env.decayDuration) * (env.sustainLevel - MODENV_PEAK) + MODENV_PEAK;
173
+ }
174
+ else
175
+ {
176
+ // sustain: stay at sustain level
177
+ env.currentValue = env.sustainLevel;
178
+ }
179
+ return env.currentValue;
180
+ }
181
+ }