spessasynth_core 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/LICENSE +3 -26
  2. package/README.md +156 -474
  3. package/index.js +73 -8
  4. package/package.json +21 -8
  5. package/src/externals/fflate/LICENSE +21 -0
  6. package/src/externals/fflate/fflate.min.js +1 -0
  7. package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  8. package/src/externals/stbvorbis_sync/LICENSE +202 -0
  9. package/src/externals/stbvorbis_sync/NOTICE +6 -0
  10. package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  11. package/src/midi/README.md +32 -0
  12. package/src/midi/basic_midi.js +567 -0
  13. package/src/midi/midi_builder.js +202 -0
  14. package/src/midi/midi_loader.js +324 -0
  15. package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
  16. package/src/midi/midi_sequence.js +224 -0
  17. package/src/midi/midi_tools/get_note_times.js +154 -0
  18. package/src/midi/midi_tools/midi_editor.js +611 -0
  19. package/src/midi/midi_tools/midi_writer.js +99 -0
  20. package/src/midi/midi_tools/rmidi_writer.js +567 -0
  21. package/src/midi/midi_tools/used_keys_loaded.js +238 -0
  22. package/src/midi/xmf_loader.js +454 -0
  23. package/src/sequencer/README.md +5 -0
  24. package/src/sequencer/events.js +81 -0
  25. package/src/sequencer/play.js +349 -0
  26. package/src/sequencer/process_event.js +165 -0
  27. package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
  28. package/src/sequencer/sequencer_engine.js +367 -0
  29. package/src/sequencer/song_control.js +201 -0
  30. package/src/soundfont/README.md +13 -0
  31. package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
  32. package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
  33. package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
  34. package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
  35. package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
  36. package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
  37. package/src/soundfont/basic_soundfont/generator.js +220 -0
  38. package/src/soundfont/basic_soundfont/modulator.js +378 -0
  39. package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
  40. package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
  41. package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
  42. package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
  43. package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
  44. package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
  45. package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
  46. package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
  47. package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
  48. package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
  49. package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
  50. package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
  51. package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
  52. package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
  53. package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
  54. package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
  55. package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
  56. package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
  57. package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
  58. package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
  59. package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
  60. package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
  61. package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
  62. package/src/soundfont/dls/articulator_converter.js +396 -0
  63. package/src/soundfont/dls/dls_destinations.js +38 -0
  64. package/src/soundfont/dls/dls_preset.js +44 -0
  65. package/src/soundfont/dls/dls_sample.js +75 -0
  66. package/src/soundfont/dls/dls_soundfont.js +186 -0
  67. package/src/soundfont/dls/dls_sources.js +62 -0
  68. package/src/soundfont/dls/dls_zone.js +95 -0
  69. package/src/soundfont/dls/read_articulation.js +299 -0
  70. package/src/soundfont/dls/read_instrument.js +121 -0
  71. package/src/soundfont/dls/read_instrument_list.js +17 -0
  72. package/src/soundfont/dls/read_lart.js +35 -0
  73. package/src/soundfont/dls/read_region.js +152 -0
  74. package/src/soundfont/dls/read_samples.js +270 -0
  75. package/src/soundfont/load_soundfont.js +21 -0
  76. package/src/soundfont/read_sf2/generators.js +46 -0
  77. package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
  78. package/src/soundfont/read_sf2/modulators.js +36 -0
  79. package/src/soundfont/read_sf2/presets.js +80 -0
  80. package/src/soundfont/read_sf2/samples.js +304 -0
  81. package/src/soundfont/read_sf2/soundfont.js +305 -0
  82. package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
  83. package/src/synthetizer/README.md +7 -0
  84. package/src/synthetizer/audio_engine/README.md +9 -0
  85. package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
  86. package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
  87. package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
  88. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
  89. package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
  90. package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
  91. package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
  92. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
  93. package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
  94. package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
  95. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
  96. package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
  97. package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
  98. package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
  99. package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
  100. package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
  101. package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
  102. package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
  103. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
  104. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
  105. package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
  106. package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
  107. package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
  108. package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
  109. package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
  110. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
  111. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
  112. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
  113. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
  114. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
  115. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
  116. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
  117. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
  118. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
  119. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
  120. package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
  121. package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
  122. package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
  123. package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
  124. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
  125. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
  126. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
  127. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
  128. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
  129. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
  130. package/src/synthetizer/audio_engine/main_processor.js +804 -0
  131. package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
  132. package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
  133. package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
  134. package/src/synthetizer/synth_constants.js +22 -0
  135. package/{spessasynth_core → src}/utils/README.md +1 -0
  136. package/src/utils/buffer_to_wav.js +185 -0
  137. package/src/utils/byte_functions/big_endian.js +32 -0
  138. package/src/utils/byte_functions/little_endian.js +77 -0
  139. package/src/utils/byte_functions/string.js +107 -0
  140. package/src/utils/byte_functions/variable_length_quantity.js +42 -0
  141. package/src/utils/fill_with_defaults.js +21 -0
  142. package/src/utils/indexed_array.js +52 -0
  143. package/{spessasynth_core → src}/utils/loggin.js +70 -78
  144. package/src/utils/other.js +92 -0
  145. package/src/utils/sysex_detector.js +58 -0
  146. package/src/utils/xg_hacks.js +193 -0
  147. package/.idea/inspectionProfiles/Project_Default.xml +0 -10
  148. package/.idea/jsLibraryMappings.xml +0 -6
  149. package/.idea/modules.xml +0 -8
  150. package/.idea/spessasynth_core.iml +0 -12
  151. package/.idea/vcs.xml +0 -6
  152. package/spessasynth_core/midi_parser/README.md +0 -3
  153. package/spessasynth_core/midi_parser/midi_loader.js +0 -386
  154. package/spessasynth_core/sequencer/sequencer.js +0 -202
  155. package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
  156. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
  157. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
  158. package/spessasynth_core/soundfont/README.md +0 -4
  159. package/spessasynth_core/soundfont/chunk/generators.js +0 -205
  160. package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
  161. package/spessasynth_core/soundfont/chunk/presets.js +0 -264
  162. package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
  163. package/spessasynth_core/soundfont/chunk/samples.js +0 -250
  164. package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
  165. package/spessasynth_core/synthetizer/README.md +0 -6
  166. package/spessasynth_core/synthetizer/synthesizer.js +0 -303
  167. package/spessasynth_core/synthetizer/worklet_system/README.md +0 -3
  168. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -290
  169. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -280
  170. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +0 -102
  171. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +0 -77
  172. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +0 -140
  173. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -266
  174. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -104
  175. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
  176. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +0 -222
  177. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -95
  178. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +0 -73
  179. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -76
  180. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -194
  181. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -83
  182. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -173
  183. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +0 -106
  184. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +0 -313
  185. package/spessasynth_core/utils/buffer_to_wav.js +0 -70
  186. package/spessasynth_core/utils/byte_functions.js +0 -141
  187. package/spessasynth_core/utils/other.js +0 -49
  188. package/spessasynth_core/utils/shiftable_array.js +0 -26
  189. package/spessasynth_core/utils/stbvorbis_sync.js +0 -1877
@@ -0,0 +1,776 @@
1
+ import { arrayToHexString, consoleColors } from "../../../utils/other.js";
2
+ import { SpessaSynthInfo, SpessaSynthWarn } from "../../../utils/loggin.js";
3
+ import { midiControllers } from "../../../midi/midi_message.js";
4
+ import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from "../../synth_constants.js";
5
+ import { isSystemXG } from "../../../utils/xg_hacks.js";
6
+ import { masterParameterType } from "./controller_control/master_parameters.js";
7
+ import { readBytesAsString } from "../../../utils/byte_functions/string.js";
8
+
9
+ /**
10
+ * KeyNum: tuning
11
+ * @typedef {MTSNoteTuning[]} MTSProgramTuning
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} MTSNoteTuning
16
+ * @property {number} midiNote - the base midi note to use, -1 means no change
17
+ * @property {number} centTuning - additional tuning
18
+ */
19
+
20
+ /**
21
+ * Calculates freqency for MIDI Tuning Standard
22
+ * @param byte1 {number}
23
+ * @param byte2 {number}
24
+ * @param byte3 {number}
25
+ * @return {{midiNote: number, centTuning: number|null}}
26
+ */
27
+ function getTuning(byte1, byte2, byte3)
28
+ {
29
+ const midiNote = byte1;
30
+ const fraction = (byte2 << 7) | byte3; // Combine byte2 and byte3 into a 14-bit number
31
+
32
+ // no change
33
+ if (byte1 === 0x7F && byte2 === 0x7F && byte3 === 0x7F)
34
+ {
35
+ return { midiNote: -1, centTuning: null };
36
+ }
37
+
38
+ // calculate cent tuning
39
+ return { midiNote: midiNote, centTuning: fraction * 0.0061 };
40
+ }
41
+
42
+ /**
43
+ * The text types for the synth display
44
+ * @enum {number}
45
+ */
46
+ export const SynthDisplayType = {
47
+ SoundCanvasText: 0,
48
+ XGText: 1,
49
+ SoundCanvasDotDisplay: 2
50
+ };
51
+
52
+
53
+ /**
54
+ * Executes a system exclusive
55
+ * @param messageData {number[]|IndexedByteArray} - the message data without f0
56
+ * @param channelOffset {number}
57
+ * @this {SpessaSynthProcessor}
58
+ */
59
+ export function systemExclusive(messageData, channelOffset = 0)
60
+ {
61
+ const type = messageData[0];
62
+ if (this.deviceID !== ALL_CHANNELS_OR_DIFFERENT_ACTION && messageData[1] !== 0x7F)
63
+ {
64
+ if (this.deviceID !== messageData[1])
65
+ {
66
+ // not our device ID
67
+ return;
68
+ }
69
+ }
70
+ switch (type)
71
+ {
72
+ default:
73
+ SpessaSynthWarn(
74
+ `%cUnrecognized SysEx: %c${arrayToHexString(messageData)}`,
75
+ consoleColors.warn,
76
+ consoleColors.unrecognized
77
+ );
78
+ break;
79
+
80
+ // non realtime
81
+ case 0x7E:
82
+ case 0x7F:
83
+ switch (messageData[2])
84
+ {
85
+ case 0x04:
86
+ let cents;
87
+ // device control
88
+ switch (messageData[3])
89
+ {
90
+ case 0x01:
91
+ // main volume
92
+ const vol = messageData[5] << 7 | messageData[4];
93
+ this.setMIDIVolume(vol / 16384);
94
+ SpessaSynthInfo(
95
+ `%cMaster Volume. Volume: %c${vol}`,
96
+ consoleColors.info,
97
+ consoleColors.value
98
+ );
99
+ break;
100
+
101
+ case 0x02:
102
+ // main balance
103
+ // midi spec page 62
104
+ const balance = messageData[5] << 7 | messageData[4];
105
+ const pan = (balance - 8192) / 8192;
106
+ this.setMasterParameter(masterParameterType.masterPan, pan);
107
+ SpessaSynthInfo(
108
+ `%cMaster Pan. Pan: %c${pan}`,
109
+ consoleColors.info,
110
+ consoleColors.value
111
+ );
112
+ break;
113
+
114
+
115
+ case 0x03:
116
+ // fine-tuning
117
+ const tuningValue = ((messageData[5] << 7) | messageData[6]) - 8192;
118
+ cents = Math.floor(tuningValue / 81.92); // [-100;+99] cents range
119
+ this.setMasterTuning(cents);
120
+ SpessaSynthInfo(
121
+ `%cMaster Fine Tuning. Cents: %c${cents}`,
122
+ consoleColors.info,
123
+ consoleColors.value
124
+ );
125
+ break;
126
+
127
+ case 0x04:
128
+ // coarse tuning
129
+ // lsb is ignored
130
+ const semitones = messageData[5] - 64;
131
+ cents = semitones * 100;
132
+ this.setMasterTuning(cents);
133
+ SpessaSynthInfo(
134
+ `%cMaster Coarse Tuning. Cents: %c${cents}`,
135
+ consoleColors.info,
136
+ consoleColors.value
137
+ );
138
+ break;
139
+
140
+ default:
141
+ SpessaSynthWarn(
142
+ `%cUnrecognized MIDI Device Control Real-time message: %c${arrayToHexString(messageData)}`,
143
+ consoleColors.warn,
144
+ consoleColors.unrecognized
145
+ );
146
+ }
147
+ break;
148
+
149
+ case 0x09:
150
+ // gm system related
151
+ if (messageData[3] === 0x01)
152
+ {
153
+ SpessaSynthInfo("%cGM1 system on", consoleColors.info);
154
+ this.setSystem("gm");
155
+ }
156
+ else if (messageData[3] === 0x03)
157
+ {
158
+ SpessaSynthInfo("%cGM2 system on", consoleColors.info);
159
+ this.setSystem("gm2");
160
+ }
161
+ else
162
+ {
163
+ SpessaSynthInfo("%cGM system off, defaulting to GS", consoleColors.info);
164
+ this.setSystem("gs");
165
+ }
166
+ break;
167
+
168
+ // MIDI Tuning standard
169
+ // https://midi.org/midi-tuning-updated-specification
170
+ case 0x08:
171
+ let currentMessageIndex = 4;
172
+ switch (messageData[3])
173
+ {
174
+ // bulk tuning dump: all 128 notes
175
+ case 0x01:
176
+ const program = messageData[currentMessageIndex++];
177
+ // read the name
178
+ messageData.currentIndex = currentMessageIndex;
179
+ const tuningName = readBytesAsString(messageData, 16);
180
+ currentMessageIndex += 16;
181
+ if (messageData.length < 384)
182
+ {
183
+ SpessaSynthWarn(`The Bulk Tuning Dump is too short! (${messageData.length} bytes, at least 384 are expected)`);
184
+ return;
185
+ }
186
+ // 128 frequencies follow
187
+ for (let i = 0; i < 128; i++)
188
+ {
189
+ // set the given tuning to the program
190
+ this.tunings[program][i] = getTuning(
191
+ messageData[currentMessageIndex++],
192
+ messageData[currentMessageIndex++],
193
+ messageData[currentMessageIndex++]
194
+ );
195
+ }
196
+ SpessaSynthInfo(
197
+ `%cBulk Tuning Dump %c${tuningName}%c Program: %c${program}`,
198
+ consoleColors.info,
199
+ consoleColors.value,
200
+ consoleColors.info,
201
+ consoleColors.recognized
202
+ );
203
+ break;
204
+
205
+ // single note change
206
+ // single note change bank
207
+ case 0x02:
208
+ case 0x07:
209
+ if (messageData[3] === 0x07)
210
+ {
211
+ // skip the bank
212
+ currentMessageIndex++;
213
+ }
214
+ // get program and number of changes
215
+ const tuningProgram = messageData[currentMessageIndex++];
216
+ const numberOfChanges = messageData[currentMessageIndex++];
217
+ for (let i = 0; i < numberOfChanges; i++)
218
+ {
219
+ // set the given tuning to the program
220
+ this.tunings[tuningProgram][messageData[currentMessageIndex++]] = getTuning(
221
+ messageData[currentMessageIndex++],
222
+ messageData[currentMessageIndex++],
223
+ messageData[currentMessageIndex++]
224
+ );
225
+ }
226
+ SpessaSynthInfo(
227
+ `%cSingle Note Tuning. Program: %c${tuningProgram}%c Keys affected: %c${numberOfChanges}`,
228
+ consoleColors.info,
229
+ consoleColors.recognized,
230
+ consoleColors.info,
231
+ consoleColors.recognized
232
+ );
233
+ break;
234
+
235
+ // octave tuning (1 byte)
236
+ // and octave tuning (2 bytes)
237
+ case 0x09:
238
+ case 0x08:
239
+ // get tuning:
240
+ const newOctaveTuning = new Int8Array(12);
241
+ // start from bit 7
242
+ if (messageData[3] === 0x08)
243
+ {
244
+ // 1 byte tuning: 0 is -64 cents, 64 is 0, 127 is +63
245
+ for (let i = 0; i < 12; i++)
246
+ {
247
+ newOctaveTuning[i] = messageData[7 + i] - 64;
248
+ }
249
+ }
250
+ else
251
+ {
252
+ // 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents, 16,383 is +100 cents
253
+ for (let i = 0; i < 24; i += 2)
254
+ {
255
+ const tuning = ((messageData[7 + i] << 7) | messageData[8 + i]) - 8192;
256
+ newOctaveTuning[i / 2] = Math.floor(tuning / 81.92); // map to [-100;+99] cents
257
+ }
258
+ }
259
+ // apply to channels (ordered from 0)
260
+ // bit 1: 14 and 15
261
+ if ((messageData[4] & 1) === 1)
262
+ {
263
+ this.midiAudioChannels[14 + channelOffset].setOctaveTuning(newOctaveTuning);
264
+ }
265
+ if (((messageData[4] >> 1) & 1) === 1)
266
+ {
267
+ this.midiAudioChannels[15 + channelOffset].setOctaveTuning(newOctaveTuning);
268
+ }
269
+
270
+ // bit 2: channels 7 to 13
271
+ for (let i = 0; i < 7; i++)
272
+ {
273
+ const bit = (messageData[5] >> i) & 1;
274
+ if (bit === 1)
275
+ {
276
+ this.midiAudioChannels[7 + i + channelOffset].setOctaveTuning(newOctaveTuning);
277
+ }
278
+ }
279
+
280
+ // bit 3: channels 0 to 16
281
+ for (let i = 0; i < 7; i++)
282
+ {
283
+ const bit = (messageData[6] >> i) & 1;
284
+ if (bit === 1)
285
+ {
286
+ this.midiAudioChannels[i + channelOffset].setOctaveTuning(newOctaveTuning);
287
+ }
288
+ }
289
+
290
+ SpessaSynthInfo(
291
+ `%cMIDI Octave Scale ${
292
+ messageData[3] === 0x08 ? "(1 byte)" : "(2 bytes)"
293
+ } tuning via Tuning: %c${newOctaveTuning.join(" ")}`,
294
+ consoleColors.info,
295
+ consoleColors.value
296
+ );
297
+ break;
298
+
299
+ default:
300
+ SpessaSynthWarn(
301
+ `%cUnrecognized MIDI Tuning standard message: %c${arrayToHexString(messageData)}`,
302
+ consoleColors.warn,
303
+ consoleColors.unrecognized
304
+ );
305
+ break;
306
+ }
307
+ break;
308
+
309
+ default:
310
+ SpessaSynthWarn(
311
+ `%cUnrecognized MIDI Realtime/non realtime message: %c${arrayToHexString(messageData)}`,
312
+ consoleColors.warn,
313
+ consoleColors.unrecognized
314
+ );
315
+
316
+ }
317
+ break;
318
+
319
+ // this is a roland sysex
320
+ // http://www.bandtrax.com.au/sysex.htm
321
+ // https://cdn.roland.com/assets/media/pdf/AT-20R_30R_MI.pdf
322
+ case 0x41:
323
+
324
+ function notRecognized()
325
+ {
326
+ // this is some other GS sysex...
327
+ SpessaSynthWarn(
328
+ `%cUnrecognized Roland %cGS %cSysEx: %c${arrayToHexString(messageData)}`,
329
+ consoleColors.warn,
330
+ consoleColors.recognized,
331
+ consoleColors.warn,
332
+ consoleColors.unrecognized
333
+ );
334
+ }
335
+
336
+ if (messageData[2] === 0x42 && messageData[3] === 0x12)
337
+ {
338
+ // this is a GS sysex
339
+ // messageData[5] and [6] is the system parameter, messageData[7] is the value
340
+ const messageValue = messageData[7];
341
+ if (messageData[6] === 0x7F)
342
+ {
343
+ // GS mode set
344
+ if (messageValue === 0x00)
345
+ {
346
+ // this is a GS reset
347
+ SpessaSynthInfo("%cGS Reset received!", consoleColors.info);
348
+ this.resetAllControllers(false);
349
+ this.setSystem("gs");
350
+ }
351
+ else if (messageValue === 0x7F)
352
+ {
353
+ // GS mode off
354
+ SpessaSynthInfo("%cGS system off, switching to GM2", consoleColors.info);
355
+ this.resetAllControllers(false);
356
+ this.setSystem("gm2");
357
+ }
358
+ return;
359
+ }
360
+ else if (messageData[4] === 0x40)
361
+ {
362
+ // this is a system parameter
363
+ if ((messageData[5] & 0x10) > 0)
364
+ {
365
+ // this is an individual part (channel) parameter
366
+ // determine the channel 0 means channel 10 (default), 1 means 1 etc.
367
+ const channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][messageData[5] & 0x0F] + channelOffset;
368
+ // for example, 0x1A means A = 11, which corresponds to channel 12 (counting from 1)
369
+ const channelObject = this.midiAudioChannels[channel];
370
+ switch (messageData[6])
371
+ {
372
+ default:
373
+ // this is some other GS sysex...
374
+ notRecognized();
375
+ break;
376
+
377
+ case 0x15:
378
+ // this is the Use for Drum Part sysex (multiple drums)
379
+ const isDrums = messageValue > 0 && messageData[5] >> 4; // if set to other than 0, is a drum channel
380
+ channelObject.setDrums(isDrums);
381
+ SpessaSynthInfo(
382
+ `%cChannel %c${channel}%c ${isDrums ?
383
+ "is now a drum channel"
384
+ :
385
+ "now isn't a drum channel"
386
+ }%c via: %c${arrayToHexString(messageData)}`,
387
+ consoleColors.info,
388
+ consoleColors.value,
389
+ consoleColors.recognized,
390
+ consoleColors.info,
391
+ consoleColors.value
392
+ );
393
+ return;
394
+
395
+ case 0x16:
396
+ // this is the pitch key shift sysex
397
+ const keyShift = messageValue - 64;
398
+ channelObject.transposeChannel(keyShift);
399
+ SpessaSynthInfo(
400
+ `%cChannel %c${channel}%c pitch shift. Semitones %c${keyShift}%c, with %c${arrayToHexString(
401
+ messageData)}`,
402
+ consoleColors.info,
403
+ consoleColors.recognized,
404
+ consoleColors.info,
405
+ consoleColors.value,
406
+ consoleColors.info,
407
+ consoleColors.value
408
+ );
409
+ return;
410
+
411
+ // pan position
412
+ case 0x1C:
413
+ // 0 is random
414
+ let panpot = messageValue;
415
+ if (panpot === 0)
416
+ {
417
+ channelObject.randomPan = true;
418
+ SpessaSynthInfo(
419
+ `%cRandom pan is set to %cON%c for %c${channel}`,
420
+ consoleColors.info,
421
+ consoleColors.recognized,
422
+ consoleColors.info,
423
+ consoleColors.value
424
+ );
425
+ }
426
+ else
427
+ {
428
+ channelObject.randomPan = false;
429
+ channelObject.controllerChange(midiControllers.pan, panpot);
430
+ }
431
+ break;
432
+
433
+ // chorus send
434
+ case 0x21:
435
+ channelObject.controllerChange(midiControllers.chorusDepth, messageValue);
436
+ break;
437
+
438
+ // reverb send
439
+ case 0x22:
440
+ channelObject.controllerChange(midiControllers.reverbDepth, messageValue);
441
+ break;
442
+
443
+ case 0x40:
444
+ case 0x41:
445
+ case 0x42:
446
+ case 0x43:
447
+ case 0x44:
448
+ case 0x45:
449
+ case 0x46:
450
+ case 0x47:
451
+ case 0x48:
452
+ case 0x49:
453
+ case 0x4A:
454
+ case 0x4B:
455
+ // scale tuning: up to 12 bytes
456
+ const tuningBytes = messageData.length - 9; // data starts at 7, minus checksum and f7
457
+ // read em bytes
458
+ const newTuning = new Int8Array(12);
459
+ for (let i = 0; i < tuningBytes; i++)
460
+ {
461
+ newTuning[i] = messageData[i + 7] - 64;
462
+ }
463
+ channelObject.setOctaveTuning(newTuning);
464
+ const cents = messageValue - 64;
465
+ SpessaSynthInfo(
466
+ `%cChannel %c${channel}%c octave scale tuning. Cents %c${newTuning.join(
467
+ " ")}%c, with %c${arrayToHexString(messageData)}`,
468
+ consoleColors.info,
469
+ consoleColors.recognized,
470
+ consoleColors.info,
471
+ consoleColors.value,
472
+ consoleColors.info,
473
+ consoleColors.value
474
+ );
475
+ channelObject.setTuning(cents);
476
+ break;
477
+ }
478
+ return;
479
+ }
480
+ else
481
+ // this is a global system parameter
482
+ if (messageData[5] === 0x00 && messageData[6] === 0x06)
483
+ {
484
+ // roland master pan
485
+ SpessaSynthInfo(
486
+ `%cRoland GS Master Pan set to: %c${messageValue}%c with: %c${arrayToHexString(
487
+ messageData)}`,
488
+ consoleColors.info,
489
+ consoleColors.value,
490
+ consoleColors.info,
491
+ consoleColors.value
492
+ );
493
+ this.setMasterParameter(masterParameterType.masterPan, (messageValue - 64) / 64);
494
+ return;
495
+ }
496
+ else if (messageData[5] === 0x00 && messageData[6] === 0x05)
497
+ {
498
+ // roland master key shift (transpose)
499
+ const transpose = messageValue - 64;
500
+ SpessaSynthInfo(
501
+ `%cRoland GS Master Key-Shift set to: %c${transpose}%c with: %c${arrayToHexString(
502
+ messageData)}`,
503
+ consoleColors.info,
504
+ consoleColors.value,
505
+ consoleColors.info,
506
+ consoleColors.value
507
+ );
508
+ this.setMasterTuning(transpose * 100);
509
+ return;
510
+ }
511
+ else if (messageData[5] === 0x00 && messageData[6] === 0x04)
512
+ {
513
+ // roland GS master volume
514
+ SpessaSynthInfo(
515
+ `%cRoland GS Master Volume set to: %c${messageValue}%c with: %c${arrayToHexString(
516
+ messageData)}`,
517
+ consoleColors.info,
518
+ consoleColors.value,
519
+ consoleColors.info,
520
+ consoleColors.value
521
+ );
522
+ this.setMIDIVolume(messageValue / 127);
523
+ return;
524
+ }
525
+ }
526
+ // this is some other GS sysex...
527
+ notRecognized();
528
+ return;
529
+ }
530
+ else if (messageData[2] === 0x45 && messageData[3] === 0x12)
531
+ {
532
+ // 0x45: GS Display Data, 0x12: DT1 (Device Transmit)
533
+ // check for embedded copyright
534
+ // (roland SC display sysex) http://www.bandtrax.com.au/sysex.htm
535
+
536
+ if (
537
+ messageData[4] === 0x10 && // Sound Canvas Display
538
+ messageData[6] === 0x00 // Data follows
539
+ )
540
+ {
541
+ if (messageData[5] === 0x00) // Display letters
542
+ {
543
+ // get the text
544
+ // and header ends with (checksum) F7
545
+ const text = new Uint8Array(messageData.slice(7, messageData.length - 2));
546
+ this.callEvent(
547
+ "synthdisplay",
548
+ {
549
+ displayData: text,
550
+ displayType: SynthDisplayType.SoundCanvasText
551
+ }
552
+ );
553
+ }
554
+ else if (messageData[5] === 0x01) // Matrix display
555
+ {
556
+ // get the data
557
+ // and header ends with (checksum) F7
558
+ const dotMatrixData = new Uint8Array(messageData.slice(7, messageData.length - 3));
559
+ this.callEvent(
560
+ "synthdisplay",
561
+ {
562
+ displayData: dotMatrixData,
563
+ displayType: SynthDisplayType.SoundCanvasDotDisplay
564
+ }
565
+ );
566
+ SpessaSynthInfo(
567
+ `%cRoland SC Display Dot Matrix via: %c${arrayToHexString(
568
+ messageData)}`,
569
+ consoleColors.info,
570
+ consoleColors.value
571
+ );
572
+ }
573
+ else
574
+ {
575
+ // this is some other GS sysex...
576
+ notRecognized();
577
+ }
578
+ }
579
+ }
580
+ else if (messageData[2] === 0x16 && messageData[3] === 0x12 && messageData[4] === 0x10)
581
+ {
582
+ // this is a roland master volume message
583
+ this.setMIDIVolume(messageData[7] / 100);
584
+ SpessaSynthInfo(
585
+ `%cRoland Master Volume control set to: %c${messageData[7]}%c via: %c${arrayToHexString(
586
+ messageData)}`,
587
+ consoleColors.info,
588
+ consoleColors.value,
589
+ consoleColors.info,
590
+ consoleColors.value
591
+ );
592
+ return;
593
+ }
594
+ else
595
+ {
596
+ // this is something else...
597
+ SpessaSynthWarn(
598
+ `%cUnrecognized Roland SysEx: %c${arrayToHexString(messageData)}`,
599
+ consoleColors.warn,
600
+ consoleColors.unrecognized
601
+ );
602
+ return;
603
+ }
604
+ break;
605
+
606
+ // yamaha
607
+ // http://www.studio4all.de/htmle/main91.html
608
+ case 0x43:
609
+ // XG sysex
610
+ if (messageData[2] === 0x4C)
611
+ {
612
+ // XG system parameter
613
+ if (messageData[3] === 0x00 && messageData[4] === 0x00)
614
+ {
615
+ switch (messageData[5])
616
+ {
617
+ // master volume
618
+ case 0x04:
619
+ const vol = messageData[6];
620
+ this.setMIDIVolume(vol / 127);
621
+ SpessaSynthInfo(
622
+ `%cXG master volume. Volume: %c${vol}`,
623
+ consoleColors.info,
624
+ consoleColors.recognized
625
+ );
626
+ break;
627
+
628
+ // master transpose
629
+ case 0x06:
630
+ const transpose = messageData[6] - 64;
631
+ this.transposeAllChannels(transpose);
632
+ SpessaSynthInfo(
633
+ `%cXG master transpose. Volume: %c${transpose}`,
634
+ consoleColors.info,
635
+ consoleColors.recognized
636
+ );
637
+ break;
638
+
639
+ // XG on
640
+ case 0x7E:
641
+ SpessaSynthInfo("%cXG system on", consoleColors.info);
642
+ this.resetAllControllers(false);
643
+ this.setSystem("xg");
644
+ break;
645
+ }
646
+ }
647
+ else
648
+ // XG part parameter
649
+ if (messageData[3] === 0x08)
650
+ {
651
+ if (!isSystemXG(this.system))
652
+ {
653
+ return;
654
+ }
655
+ const channel = messageData[4] + channelOffset;
656
+ if (channel >= this.midiAudioChannels.length)
657
+ {
658
+ // invalid channel
659
+ return;
660
+ }
661
+ const channelObject = this.midiAudioChannels[channel];
662
+ const value = messageData[6];
663
+ switch (messageData[5])
664
+ {
665
+ // bank-select MSB
666
+ case 0x01:
667
+ channelObject.controllerChange(midiControllers.bankSelect, value);
668
+ break;
669
+
670
+ // bank-select LSB
671
+ case 0x02:
672
+ channelObject.controllerChange(midiControllers.lsbForControl0BankSelect, value);
673
+ break;
674
+
675
+ // program change
676
+ case 0x03:
677
+ channelObject.programChange(value);
678
+ break;
679
+
680
+ // note shift
681
+ case 0x08:
682
+ if (channelObject.drumChannel)
683
+ {
684
+ return;
685
+ }
686
+ const semitones = value - 64;
687
+ channelObject.channelTransposeKeyShift = semitones;
688
+ break;
689
+
690
+ // volume
691
+ case 0x0B:
692
+ channelObject.controllerChange(midiControllers.mainVolume, value);
693
+ break;
694
+
695
+ // pan position
696
+ case 0x0E:
697
+ let pan = value;
698
+ if (pan === 0)
699
+ {
700
+ // 0 means random
701
+ channelObject.randomPan = true;
702
+ SpessaSynthInfo(
703
+ `%cRandom pan is set to %cON%c for %c${channel}`,
704
+ consoleColors.info,
705
+ consoleColors.recognized,
706
+ consoleColors.info,
707
+ consoleColors.value
708
+ );
709
+ }
710
+ else
711
+ {
712
+ channelObject.controllerChange(midiControllers.pan, pan);
713
+ }
714
+ break;
715
+
716
+ // reverb
717
+ case 0x13:
718
+ channelObject.controllerChange(midiControllers.reverbDepth, value);
719
+ break;
720
+
721
+ // chorus
722
+ case 0x12:
723
+ channelObject.controllerChange(midiControllers.chorusDepth, value);
724
+ break;
725
+
726
+ default:
727
+ SpessaSynthWarn(
728
+ `%cUnrecognized Yamaha XG Part Setup: %c${messageData[5].toString(16)
729
+ .toUpperCase()}`,
730
+ consoleColors.warn,
731
+ consoleColors.unrecognized
732
+ );
733
+ }
734
+ }
735
+ else if (
736
+ messageData[3] === 0x06 && // XG System parameter
737
+ messageData[4] === 0x00 // System Byte
738
+ )
739
+ {
740
+ // displayed letters (remove F7 at the end)
741
+ // include byte 5 as it seems to be line information (useful)
742
+ const textData = new Uint8Array(messageData.slice(5, messageData.length - 1));
743
+ this.callEvent(
744
+ "synthdisplay",
745
+ {
746
+ displayData: textData,
747
+ displayType: SynthDisplayType.XGText
748
+ }
749
+ );
750
+ }
751
+ else if (isSystemXG(this.system))
752
+ {
753
+ SpessaSynthWarn(
754
+ `%cUnrecognized Yamaha XG SysEx: %c${arrayToHexString(messageData)}`,
755
+ consoleColors.warn,
756
+ consoleColors.unrecognized
757
+ );
758
+ }
759
+
760
+ }
761
+ else
762
+ {
763
+ if (isSystemXG(this.system))
764
+ {
765
+ SpessaSynthWarn(
766
+ `%cUnrecognized Yamaha SysEx: %c${arrayToHexString(messageData)}`,
767
+ consoleColors.warn,
768
+ consoleColors.unrecognized
769
+ );
770
+ }
771
+ }
772
+ break;
773
+
774
+
775
+ }
776
+ }