spessasynth_core 1.1.3 → 1.1.5

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 +74 -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,66 @@
1
+ import { consoleColors } from "../../../../utils/other.js";
2
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
3
+ import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
4
+ import { customControllers, dataEntryStates, NON_CC_INDEX_OFFSET } from "../../engine_components/controller_tables.js";
5
+ import { midiControllers } from "../../../../midi/midi_message.js";
6
+
7
+ /**
8
+ * Executes a data entry for an RPN tuning
9
+ * @param dataValue {number} dataEntry LSB
10
+ * @this {MidiAudioChannel}
11
+ * @private
12
+ */
13
+ export function dataEntryFine(dataValue)
14
+ {
15
+ switch (this.dataEntryState)
16
+ {
17
+ default:
18
+ break;
19
+
20
+ case dataEntryStates.RPCoarse:
21
+ case dataEntryStates.RPFine:
22
+ const rpnValue = this.midiControllers[midiControllers.RPNMsb] | (this.midiControllers[midiControllers.RPNLsb] >> 7);
23
+ switch (rpnValue)
24
+ {
25
+ default:
26
+ break;
27
+
28
+ // pitch bend range fine tune
29
+ case 0x0000:
30
+ if (dataValue === 0)
31
+ {
32
+ break;
33
+ }
34
+ // 14-bit value, so upper 7 are coarse and lower 7 are fine!
35
+ this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] |= dataValue;
36
+ const actualTune = (this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] >> 7) + dataValue / 128;
37
+ SpessaSynthInfo(
38
+ `%cChannel ${this.channelNumber} bend range. Semitones: %c${actualTune}`,
39
+ consoleColors.info,
40
+ consoleColors.value
41
+ );
42
+ break;
43
+
44
+ // fine-tuning
45
+ case 0x0001:
46
+ // grab the data and shift
47
+ const coarse = this.customControllers[customControllers.channelTuning];
48
+ const finalTuning = (coarse << 7) | dataValue;
49
+ this.setTuning(finalTuning * 0.01220703125); // multiply by 8192 / 100 (cent increments)
50
+ break;
51
+
52
+ // modulation depth
53
+ case 0x0005:
54
+ const currentModulationDepthCents = this.customControllers[customControllers.modulationMultiplier] * 50;
55
+ let cents = currentModulationDepthCents + (dataValue / 128) * 100;
56
+ this.setModulationDepth(cents);
57
+ break;
58
+
59
+ case 0x3FFF:
60
+ this.resetParameters();
61
+ break;
62
+
63
+ }
64
+
65
+ }
66
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @param isMuted {boolean}
3
+ * @this {MidiAudioChannel}
4
+ */
5
+ export function muteChannel(isMuted)
6
+ {
7
+ if (isMuted)
8
+ {
9
+ this.stopAllNotes(true);
10
+ }
11
+ this.isMuted = isMuted;
12
+ this.sendChannelProperty();
13
+ this.synth.callEvent("mutechannel", {
14
+ channel: this.channelNumber,
15
+ isMuted: isMuted
16
+ });
17
+ }
@@ -0,0 +1,175 @@
1
+ import { computeModulators } from "../engine_components/compute_modulator.js";
2
+ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
3
+ import { midiControllers } from "../../../midi/midi_message.js";
4
+ import { portamentoTimeToSeconds } from "./portamento_time.js";
5
+
6
+ /**
7
+ * sends a "MIDI Note on message"
8
+ * @param midiNote {number}
9
+ * @param velocity {number}
10
+ * @this {MidiAudioChannel}
11
+ */
12
+ export function noteOn(midiNote, velocity)
13
+ {
14
+ if (velocity < 1)
15
+ {
16
+ this.noteOff(midiNote);
17
+ return;
18
+ }
19
+ velocity = Math.min(127, velocity);
20
+
21
+ if (
22
+ (this.synth.highPerformanceMode && this.synth.totalVoicesAmount > 200 && velocity < 40) ||
23
+ (this.synth.highPerformanceMode && velocity < 10) ||
24
+ (this.isMuted)
25
+ )
26
+ {
27
+ return;
28
+ }
29
+
30
+ const realKey = midiNote + this.channelTransposeKeyShift;
31
+ let sentMidiNote = realKey;
32
+
33
+ if (realKey > 127 || realKey < 0)
34
+ {
35
+ return;
36
+ }
37
+ const program = this.preset.program;
38
+ const tune = this.synth.tunings[program]?.[realKey]?.midiNote;
39
+ if (tune >= 0)
40
+ {
41
+ sentMidiNote = tune;
42
+ }
43
+
44
+ // velocity override
45
+ if (this.velocityOverride > 0)
46
+ {
47
+ velocity = this.velocityOverride;
48
+ }
49
+
50
+ // key velocity override
51
+ const keyVel = this.synth.keyModifierManager.getVelocity(this.channelNumber, realKey);
52
+ if (keyVel > -1)
53
+ {
54
+ velocity = keyVel;
55
+ }
56
+
57
+ // gain
58
+ const voiceGain = this.synth.keyModifierManager.getGain(this.channelNumber, realKey);
59
+
60
+ // portamento
61
+ let portamentoFromKey = -1;
62
+ let portamentoDuration = 0;
63
+ // note: the 14-bit value needs to go down to 7-bit
64
+ const portamentoTime = this.midiControllers[midiControllers.portamentoTime] >> 7;
65
+ const control = this.midiControllers[midiControllers.portamentoControl];
66
+ const currentFromKey = control >> 7;
67
+ if (
68
+ !this.drumChannel && // no portamento on drum channel
69
+ currentFromKey !== sentMidiNote && // if the same note, there's no portamento
70
+ this.midiControllers[midiControllers.portamentoOnOff] >= 8192 && // (64 << 7)
71
+ portamentoTime > 0 // 0 duration is no portamento
72
+ )
73
+ {
74
+ // a value of one means the initial portamento
75
+ if (control !== 1)
76
+ {
77
+ const diff = Math.abs(sentMidiNote - currentFromKey);
78
+ portamentoDuration = portamentoTimeToSeconds(portamentoTime, diff);
79
+ portamentoFromKey = currentFromKey;
80
+ }
81
+ // set portamento control to previous value
82
+ this.controllerChange(midiControllers.portamentoControl, sentMidiNote);
83
+ }
84
+
85
+ // get voices
86
+ const voices = this.synth.getVoices(
87
+ this.channelNumber,
88
+ sentMidiNote,
89
+ velocity,
90
+ realKey
91
+ );
92
+
93
+ // zero means disabled
94
+ let panOverride = 0;
95
+ if (this.randomPan)
96
+ {
97
+ // the range is -500 to 500
98
+ panOverride = Math.round(Math.random() * 1000 - 500);
99
+ }
100
+
101
+ // add voices
102
+ const channelVoices = this.voices;
103
+ voices.forEach(voice =>
104
+ {
105
+ // apply portamento
106
+ voice.portamentoFromKey = portamentoFromKey;
107
+ voice.portamentoDuration = portamentoDuration;
108
+
109
+ // apply pan override
110
+ voice.overridePan = panOverride;
111
+
112
+ // apply gain override
113
+ voice.gain = voiceGain;
114
+
115
+ // apply exclusive class
116
+ const exclusive = voice.exclusiveClass;
117
+ if (exclusive !== 0)
118
+ {
119
+ // kill all voices with the same exclusive class
120
+ channelVoices.forEach(v =>
121
+ {
122
+ if (v.exclusiveClass === exclusive)
123
+ {
124
+ v.exclusiveRelease(this.synth.currentSynthTime);
125
+ }
126
+ });
127
+ }
128
+ // compute all modulators
129
+ computeModulators(voice, this.midiControllers);
130
+ // modulate sample offsets (these are not real time)
131
+ const cursorStartOffset = voice.modulatedGenerators[generatorTypes.startAddrsOffset] + voice.modulatedGenerators[generatorTypes.startAddrsCoarseOffset] * 32768;
132
+ const endOffset = voice.modulatedGenerators[generatorTypes.endAddrOffset] + voice.modulatedGenerators[generatorTypes.endAddrsCoarseOffset] * 32768;
133
+ const loopStartOffset = voice.modulatedGenerators[generatorTypes.startloopAddrsOffset] + voice.modulatedGenerators[generatorTypes.startloopAddrsCoarseOffset] * 32768;
134
+ const loopEndOffset = voice.modulatedGenerators[generatorTypes.endloopAddrsOffset] + voice.modulatedGenerators[generatorTypes.endloopAddrsCoarseOffset] * 32768;
135
+ const sm = voice.sample;
136
+ // apply them
137
+ const clamp = num => Math.max(0, Math.min(sm.sampleData.length - 1, num));
138
+ sm.cursor = clamp(sm.cursor + cursorStartOffset);
139
+ sm.end = clamp(sm.end + endOffset);
140
+ sm.loopStart = clamp(sm.loopStart + loopStartOffset);
141
+ sm.loopEnd = clamp(sm.loopEnd + loopEndOffset);
142
+ // swap loops if needed
143
+ if (sm.loopEnd < sm.loopStart)
144
+ {
145
+ const temp = sm.loopStart;
146
+ sm.loopStart = sm.loopEnd;
147
+ sm.loopEnd = temp;
148
+ }
149
+ if (sm.loopEnd - sm.loopStart < 1)
150
+ {
151
+ sm.loopingMode = 0;
152
+ sm.isLooping = false;
153
+ }
154
+ // set the current attenuation to target,
155
+ // as it's interpolated (we don't want 0 attenuation for even a split second)
156
+ voice.volumeEnvelope.attenuation = voice.volumeEnvelope.attenuationTargetGain;
157
+ // set initial pan to avoid split second changing from middle to the correct value
158
+ voice.currentPan = Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan])); // -500 to 500
159
+ });
160
+
161
+ this.synth.totalVoicesAmount += voices.length;
162
+ // cap the voices
163
+ if (this.synth.totalVoicesAmount > this.synth.voiceCap)
164
+ {
165
+ this.synth.voiceKilling(voices.length);
166
+ }
167
+ channelVoices.push(...voices);
168
+ this.sendChannelProperty();
169
+ this.synth.callEvent("noteon", {
170
+ midiNote: midiNote,
171
+ channel: this.channelNumber,
172
+ velocity: velocity
173
+ });
174
+
175
+ }
@@ -0,0 +1,92 @@
1
+ // Tests were performed by John Novak
2
+ // https://github.com/dosbox-staging/dosbox-staging/pull/2705
3
+
4
+ /*
5
+ CC 5 value Portamento time
6
+ ---------- ---------------
7
+ 0 0.000 s
8
+ 1 0.006 s
9
+ 2 0.023 s
10
+ 4 0.050 s
11
+ 8 0.110 s
12
+ 16 0.250 s
13
+ 32 0.500 s
14
+ 64 2.060 s
15
+ 80 4.200 s
16
+ 96 8.400 s
17
+ 112 19.500 s
18
+ 116 26.700 s
19
+ 120 40.000 s
20
+ 124 80.000 s
21
+ 127 480.000 s
22
+ */
23
+
24
+ const portamentoLookup = {
25
+ 0: 0.000,
26
+ 1: 0.006,
27
+ 2: 0.023,
28
+ 4: 0.050,
29
+ 8: 0.110,
30
+ 16: 0.250,
31
+ 32: 0.500,
32
+ 64: 2.060,
33
+ 80: 4.200,
34
+ 96: 8.400,
35
+ 112: 19.500,
36
+ 116: 26.700,
37
+ 120: 40.000,
38
+ 124: 80.000,
39
+ 127: 480.000
40
+ };
41
+
42
+ /**
43
+ * @param value {number}
44
+ * @returns {number}
45
+ */
46
+ function getLookup(value)
47
+ {
48
+ if (portamentoLookup[value] !== undefined)
49
+ {
50
+ return portamentoLookup[value];
51
+ }
52
+ // get the nearest lower and upper points from the lookup table
53
+ let lower = null;
54
+ let upper = null;
55
+
56
+ for (let key of Object.keys(portamentoLookup))
57
+ {
58
+ key = parseInt(key);
59
+ if (key < value && (lower === null || key > lower))
60
+ {
61
+ lower = key;
62
+ }
63
+ if (key > value && (upper === null || key < upper))
64
+ {
65
+ upper = key;
66
+ }
67
+ }
68
+
69
+ // if we have found both lower and upper points, perform linear interpolation
70
+ if (lower !== null && upper !== null)
71
+ {
72
+ let lowerTime = portamentoLookup[lower];
73
+ let upperTime = portamentoLookup[upper];
74
+
75
+ // linear interpolation
76
+ return lowerTime + ((value - lower) * (upperTime - lowerTime)) / (upper - lower);
77
+ }
78
+ return 0;
79
+ }
80
+
81
+
82
+ /**
83
+ * Converts portamento time to seconds
84
+ * @param time {number} 0 - 127
85
+ * @param distance {number} distance in keys
86
+ * @returns {number} seconds
87
+ */
88
+ export function portamentoTimeToSeconds(time, distance)
89
+ {
90
+ // this seems to work fine for the MIDIs I have
91
+ return getLookup(time) * (distance / 30);
92
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * executes a program change
3
+ * @param programNumber {number}
4
+ * @this {MidiAudioChannel}
5
+ */
6
+ export function programChange(programNumber)
7
+ {
8
+ if (this.lockPreset)
9
+ {
10
+ return;
11
+ }
12
+ // always 128 for percussion
13
+ let bank = this.getBankSelect();
14
+ let sentBank;
15
+ /**
16
+ * @type {BasicPreset}
17
+ */
18
+ let preset;
19
+
20
+ const isXG = this.isXGChannel;
21
+ // check if override
22
+ if (this.synth.overrideSoundfont)
23
+ {
24
+ const bankWithOffset = bank === 128 ? 128 : bank - this.synth.soundfontBankOffset;
25
+ const p = this.synth.overrideSoundfont.getPresetNoFallback(
26
+ bankWithOffset,
27
+ programNumber,
28
+ isXG
29
+ );
30
+ if (p)
31
+ {
32
+ sentBank = p.bank === 128 ? 128 : p.bank + this.synth.soundfontBankOffset;
33
+ preset = p;
34
+ this.presetUsesOverride = true;
35
+ }
36
+ else
37
+ {
38
+ preset = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
39
+ const offset = this.synth.soundfontManager.soundfontList
40
+ .find(s => s.soundfont === preset.parentSoundBank).bankOffset;
41
+ sentBank = preset.bank - offset;
42
+ this.presetUsesOverride = false;
43
+ }
44
+ }
45
+ else
46
+ {
47
+ preset = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
48
+ const offset = this.synth.soundfontManager.soundfontList
49
+ .find(s => s.soundfont === preset.parentSoundBank).bankOffset;
50
+ sentBank = preset.bank - offset;
51
+ this.presetUsesOverride = false;
52
+ }
53
+ this.setPreset(preset);
54
+ this.sentBank = sentBank;
55
+ this.synth.callEvent("programchange", {
56
+ channel: this.channelNumber,
57
+ program: preset.program,
58
+ bank: sentBank
59
+ });
60
+ this.sendChannelProperty();
61
+ }
@@ -0,0 +1,196 @@
1
+ import { VolumeEnvelope } from "../engine_components/volume_envelope.js";
2
+ import { ModulationEnvelope } from "../engine_components/modulation_envelope.js";
3
+ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
4
+ import { customControllers } from "../engine_components/controller_tables.js";
5
+ import { absCentsToHz, timecentsToSeconds } from "../engine_components/unit_converter.js";
6
+ import { getLFOValue } from "../engine_components/lfo.js";
7
+ import { interpolationTypes, WavetableOscillator } from "../engine_components/wavetable_oscillator.js";
8
+ import { WorkletLowpassFilter } from "../engine_components/lowpass_filter.js";
9
+
10
+ /**
11
+ * Renders a voice to the stereo output buffer
12
+ * @param voice {Voice} the voice to render
13
+ * @param timeNow {number} current time in seconds
14
+ * @param outputLeft {Float32Array} the left output buffer
15
+ * @param outputRight {Float32Array} the right output buffer
16
+ * @param reverbOutputLeft {Float32Array} left output for reverb
17
+ * @param reverbOutputRight {Float32Array} right output for reverb
18
+ * @param chorusOutputLeft {Float32Array} left output for chorus
19
+ * @param chorusOutputRight {Float32Array} right output for chorus
20
+ * @this {MidiAudioChannel}
21
+ * @returns {boolean} true if the voice is finished
22
+ */
23
+ export function renderVoice(
24
+ voice, timeNow,
25
+ outputLeft, outputRight,
26
+ reverbOutputLeft, reverbOutputRight,
27
+ chorusOutputLeft, chorusOutputRight
28
+ )
29
+ {
30
+ // check if release
31
+ if (!voice.isInRelease)
32
+ {
33
+ // if not in release, check if the release time is
34
+ if (timeNow >= voice.releaseStartTime)
35
+ {
36
+ // release the voice here
37
+ voice.isInRelease = true;
38
+ VolumeEnvelope.startRelease(voice);
39
+ ModulationEnvelope.startRelease(voice);
40
+ if (voice.sample.loopingMode === 3)
41
+ {
42
+ voice.sample.isLooping = false;
43
+ }
44
+ }
45
+ }
46
+
47
+
48
+ // if the initial attenuation is more than 100dB, skip the voice (it's silent anyway)
49
+ if (voice.modulatedGenerators[generatorTypes.initialAttenuation] > 2500)
50
+ {
51
+ if (voice.isInRelease)
52
+ {
53
+ voice.finished = true;
54
+ }
55
+ return voice.finished;
56
+ }
57
+
58
+ // TUNING
59
+ let targetKey = voice.targetKey;
60
+
61
+ // calculate tuning
62
+ let cents = voice.modulatedGenerators[generatorTypes.fineTune] // soundfont fine tune
63
+ + this.channelOctaveTuning[voice.midiNote] // MTS octave tuning
64
+ + this.channelTuningCents; // channel tuning
65
+ let semitones = voice.modulatedGenerators[generatorTypes.coarseTune]; // soundfont coarse tuning
66
+
67
+ // midi tuning standard
68
+ const tuning = this.synth.tunings[this.preset.program]?.[voice.realKey];
69
+ if (tuning !== undefined && tuning?.midiNote >= 0)
70
+ {
71
+ // override key
72
+ targetKey = tuning.midiNote;
73
+ // add micro-tonal tuning
74
+ cents += tuning.centTuning;
75
+ }
76
+
77
+ // portamento
78
+ if (voice.portamentoFromKey > -1)
79
+ {
80
+ // 0 to 1
81
+ const elapsed = Math.min((timeNow - voice.startTime) / voice.portamentoDuration, 1);
82
+ const diff = targetKey - voice.portamentoFromKey;
83
+ // zero progress means the pitch being in fromKey, full progress means the normal pitch
84
+ semitones -= diff * (1 - elapsed);
85
+ }
86
+
87
+ // calculate tuning by key using soundfont's scale tuning
88
+ cents += (targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
89
+
90
+ // vibrato LFO
91
+ const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
92
+ if (vibratoDepth !== 0)
93
+ {
94
+ // calculate start time and lfo value
95
+ const vibStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVibLFO]);
96
+ const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
97
+ const lfoVal = getLFOValue(vibStart, vibFreqHz, timeNow);
98
+ // use modulation multiplier (RPN modulation depth)
99
+ cents += lfoVal * (vibratoDepth * this.customControllers[customControllers.modulationMultiplier]);
100
+ }
101
+
102
+ // low pass excursion with LFO and mod envelope
103
+ let lowpassExcursion = 0;
104
+
105
+ // mod LFO
106
+ const modPitchDepth = voice.modulatedGenerators[generatorTypes.modLfoToPitch];
107
+ const modVolDepth = voice.modulatedGenerators[generatorTypes.modLfoToVolume];
108
+ const modFilterDepth = voice.modulatedGenerators[generatorTypes.modLfoToFilterFc];
109
+ let modLfoCentibels = 0;
110
+ // don't compute mod lfo unless necessary
111
+ if (modPitchDepth !== 0 || modFilterDepth !== 0 || modVolDepth !== 0)
112
+ {
113
+ // calculate start time and lfo value
114
+ const modStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModLFO]);
115
+ const modFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqModLFO]);
116
+ const modLfoValue = getLFOValue(modStart, modFreqHz, timeNow);
117
+ // use modulation multiplier (RPN modulation depth)
118
+ cents += modLfoValue * (modPitchDepth * this.customControllers[customControllers.modulationMultiplier]);
119
+ // vole nv volume offset
120
+ // negate the lfo value because audigy starts with increase rather than decrease
121
+ modLfoCentibels = -modLfoValue * modVolDepth;
122
+ // low pass frequency
123
+ lowpassExcursion += modLfoValue * modFilterDepth;
124
+ }
125
+
126
+ // channel vibrato (GS NRPN)
127
+ if (this.channelVibrato.depth > 0)
128
+ {
129
+ // same as others
130
+ const channelVibrato = getLFOValue(
131
+ voice.startTime + this.channelVibrato.delay,
132
+ this.channelVibrato.rate,
133
+ timeNow
134
+ );
135
+ if (channelVibrato)
136
+ {
137
+ cents += channelVibrato * this.channelVibrato.depth;
138
+ }
139
+ }
140
+
141
+ // mod env
142
+ const modEnvPitchDepth = voice.modulatedGenerators[generatorTypes.modEnvToPitch];
143
+ const modEnvFilterDepth = voice.modulatedGenerators[generatorTypes.modEnvToFilterFc];
144
+ // don't compute mod env unless necessary
145
+ if (modEnvFilterDepth !== 0 || modEnvPitchDepth !== 0)
146
+ {
147
+ const modEnv = ModulationEnvelope.getValue(voice, timeNow);
148
+ // apply values
149
+ lowpassExcursion += modEnv * modEnvFilterDepth;
150
+ cents += modEnv * modEnvPitchDepth;
151
+ }
152
+
153
+ // finally, calculate the playback rate
154
+ const centsTotal = ~~(cents + semitones * 100);
155
+ if (centsTotal !== voice.currentTuningCents)
156
+ {
157
+ voice.currentTuningCents = centsTotal;
158
+ voice.currentTuningCalculated = Math.pow(2, centsTotal / 1200);
159
+ }
160
+
161
+
162
+ // SYNTHESIS
163
+ const bufferOut = new Float32Array(outputLeft.length);
164
+
165
+ // wave table oscillator
166
+ switch (this.synth.interpolationType)
167
+ {
168
+ case interpolationTypes.fourthOrder:
169
+ WavetableOscillator.getSampleCubic(voice, bufferOut);
170
+ break;
171
+
172
+ case interpolationTypes.linear:
173
+ default:
174
+ WavetableOscillator.getSampleLinear(voice, bufferOut);
175
+ break;
176
+
177
+ case interpolationTypes.nearestNeighbor:
178
+ WavetableOscillator.getSampleNearest(voice, bufferOut);
179
+ break;
180
+ }
181
+
182
+ // low pass filter
183
+ WorkletLowpassFilter.apply(voice, bufferOut, lowpassExcursion, this.synth.filterSmoothingFactor);
184
+
185
+ // vol env
186
+ VolumeEnvelope.apply(voice, bufferOut, modLfoCentibels, this.synth.volumeEnvelopeSmoothingFactor);
187
+
188
+ this.panVoice(
189
+ voice,
190
+ bufferOut,
191
+ outputLeft, outputRight,
192
+ reverbOutputLeft, reverbOutputRight,
193
+ chorusOutputLeft, chorusOutputRight
194
+ );
195
+ return voice.finished;
196
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @this {SpessaSynthProcessor}
3
+ * @param sendPresets {boolean}
4
+ * @param clearOverride {boolean}
5
+ */
6
+ export function clearSoundFont(sendPresets = true, clearOverride = true)
7
+ {
8
+ this.stopAllChannels(true);
9
+ if (clearOverride)
10
+ {
11
+ delete this.overrideSoundfont;
12
+ this.overrideSoundfont = undefined;
13
+ }
14
+ this.getDefaultPresets();
15
+ this.cachedVoices = [];
16
+
17
+ for (let i = 0; i < this.midiAudioChannels.length; i++)
18
+ {
19
+ const channelObject = this.midiAudioChannels[i];
20
+ if (!clearOverride || (clearOverride && channelObject.presetUsesOverride))
21
+ {
22
+ channelObject.setPresetLock(false);
23
+ }
24
+ channelObject.programChange(channelObject.preset.program);
25
+ }
26
+ if (sendPresets)
27
+ {
28
+ this.sendPresetList();
29
+ }
30
+ }
@@ -0,0 +1,22 @@
1
+ import { isSystemXG } from "../../../../utils/xg_hacks.js";
2
+
3
+ /**
4
+ * @this {SpessaSynthProcessor}
5
+ * @param program {number}
6
+ * @param bank {number}
7
+ * @returns {BasicPreset}
8
+ */
9
+ export function getPreset(bank, program)
10
+ {
11
+ if (this.overrideSoundfont)
12
+ {
13
+ // if override soundfont
14
+ const bankWithOffset = bank === 128 ? 128 : bank - this.soundfontBankOffset;
15
+ const preset = this.overrideSoundfont.getPresetNoFallback(bankWithOffset, program, isSystemXG(this.system));
16
+ if (preset)
17
+ {
18
+ return preset;
19
+ }
20
+ }
21
+ return this.soundfontManager.getPreset(bank, program, isSystemXG(this.system));
22
+ }
@@ -0,0 +1,28 @@
1
+ import { loadSoundFont } from "../../../../soundfont/load_soundfont.js";
2
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
3
+ import { consoleColors } from "../../../../utils/other.js";
4
+
5
+ /**
6
+ * @param buffer {ArrayBuffer}
7
+ * @param isOverride {Boolean}
8
+ * @this {SpessaSynthProcessor}
9
+ */
10
+ export function reloadSoundFont(buffer, isOverride = false)
11
+ {
12
+ this.clearSoundFont(false, isOverride);
13
+ if (isOverride)
14
+ {
15
+ this.overrideSoundfont = loadSoundFont(buffer);
16
+ }
17
+ else
18
+ {
19
+ this.soundfontManager.reloadManager(buffer);
20
+ }
21
+ this.getDefaultPresets();
22
+ this.midiAudioChannels.forEach(c =>
23
+ c.programChange(c.preset.program)
24
+ );
25
+ this.postReady();
26
+ this.sendPresetList();
27
+ SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
28
+ }