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,81 @@
1
+ import { messageTypes, midiControllers } from "../midi/midi_message.js";
2
+
3
+ import { MIDI_CHANNEL_COUNT } from "../synthetizer/synth_constants.js";
4
+
5
+ /**
6
+ * @param message {number[]}
7
+ * @this {SpessaSynthSequencer}
8
+ */
9
+ export function sendMIDIMessage(message)
10
+ {
11
+ if (!this.sendMIDIMessages)
12
+ {
13
+ return;
14
+ }
15
+ this?.onMIDIMessage?.(message);
16
+ }
17
+
18
+ /**
19
+ * @this {SpessaSynthSequencer}
20
+ * @param channel {number}
21
+ * @param type {number}
22
+ * @param value {number}
23
+ */
24
+ export function sendMIDICC(channel, type, value)
25
+ {
26
+ channel %= 16;
27
+ if (!this.sendMIDIMessages)
28
+ {
29
+ return;
30
+ }
31
+ this.sendMIDIMessage([messageTypes.controllerChange | channel, type, value]);
32
+ }
33
+
34
+ /**
35
+ * @this {SpessaSynthSequencer}
36
+ * @param channel {number}
37
+ * @param program {number}
38
+ */
39
+ export function sendMIDIProgramChange(channel, program)
40
+ {
41
+ channel %= 16;
42
+ if (!this.sendMIDIMessages)
43
+ {
44
+ return;
45
+ }
46
+ this.sendMIDIMessage([messageTypes.programChange | channel, program]);
47
+ }
48
+
49
+ /**
50
+ * Sets the pitch of the given channel
51
+ * @this {SpessaSynthSequencer}
52
+ * @param channel {number} usually 0-15: the channel to change pitch
53
+ * @param MSB {number} SECOND byte of the MIDI pitchWheel message
54
+ * @param LSB {number} FIRST byte of the MIDI pitchWheel message
55
+ */
56
+ export function sendMIDIPitchWheel(channel, MSB, LSB)
57
+ {
58
+ channel %= 16;
59
+ if (!this.sendMIDIMessages)
60
+ {
61
+ return;
62
+ }
63
+ this.sendMIDIMessage([messageTypes.pitchBend | channel, LSB, MSB]);
64
+ }
65
+
66
+ /**
67
+ * @this {SpessaSynthSequencer}
68
+ */
69
+ export function sendMIDIReset()
70
+ {
71
+ if (!this.sendMIDIMessages)
72
+ {
73
+ return;
74
+ }
75
+ this.sendMIDIMessage([messageTypes.reset]);
76
+ for (let ch = 0; ch < MIDI_CHANNEL_COUNT; ch++)
77
+ {
78
+ this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.allSoundOff, 0]);
79
+ this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.resetAllControllers, 0]);
80
+ }
81
+ }
@@ -0,0 +1,349 @@
1
+ import { getEvent, messageTypes, midiControllers } from "../midi/midi_message.js";
2
+ import { resetArray } from "../synthetizer/audio_engine/engine_components/controller_tables.js";
3
+ import { nonResetableCCs } from "../synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js";
4
+
5
+
6
+ // an array with preset default values
7
+ const defaultControllerArray = resetArray.slice(0, 128);
8
+
9
+ /**
10
+ * plays from start to the target time, excluding note messages (to get the synth to the correct state)
11
+ * @private
12
+ * @param time {number} in seconds
13
+ * @param ticks {number} optional MIDI ticks, when given is used instead of time
14
+ * @returns {boolean} true if the midi file is not finished
15
+ * @this {SpessaSynthSequencer}
16
+ */
17
+ export function _playTo(time, ticks = undefined)
18
+ {
19
+ this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
20
+ // reset
21
+ this.synth.resetAllControllers();
22
+ this.sendMIDIReset();
23
+ this._resetTimers();
24
+
25
+ const channelsToSave = this.synth.midiAudioChannels.length;
26
+ /**
27
+ * save pitch bends here and send them only after
28
+ * @type {number[]}
29
+ */
30
+ const pitchBends = Array(channelsToSave).fill(8192);
31
+
32
+ /**
33
+ * Save programs here and send them only after
34
+ * @type {{program: number, bank: number, actualBank: number}[]}
35
+ */
36
+ const programs = [];
37
+ for (let i = 0; i < channelsToSave; i++)
38
+ {
39
+ programs.push({
40
+ program: -1,
41
+ bank: 0,
42
+ actualBank: 0
43
+ });
44
+ }
45
+
46
+ const isCCNonSkippable = controllerNumber => (
47
+ controllerNumber === midiControllers.dataDecrement ||
48
+ controllerNumber === midiControllers.dataIncrement ||
49
+ controllerNumber === midiControllers.dataEntryMsb ||
50
+ controllerNumber === midiControllers.dataDecrement ||
51
+ controllerNumber === midiControllers.lsbForControl6DataEntry ||
52
+ controllerNumber === midiControllers.RPNLsb ||
53
+ controllerNumber === midiControllers.RPNMsb ||
54
+ controllerNumber === midiControllers.NRPNLsb ||
55
+ controllerNumber === midiControllers.NRPNMsb ||
56
+ controllerNumber === midiControllers.bankSelect ||
57
+ controllerNumber === midiControllers.lsbForControl0BankSelect ||
58
+ controllerNumber === midiControllers.resetAllControllers
59
+ );
60
+
61
+ /**
62
+ * Save controllers here and send them only after
63
+ * @type {number[][]}
64
+ */
65
+ const savedControllers = [];
66
+ for (let i = 0; i < channelsToSave; i++)
67
+ {
68
+ savedControllers.push(Array.from(defaultControllerArray));
69
+ }
70
+
71
+ /**
72
+ * RP-15 compliant reset
73
+ * https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
74
+ * @param chan {number}
75
+ */
76
+ function resetAllControlllers(chan)
77
+ {
78
+ // reset pitch bend
79
+ pitchBends[chan] = 8192;
80
+ if (savedControllers?.[chan] === undefined)
81
+ {
82
+ return;
83
+ }
84
+ for (let i = 0; i < defaultControllerArray.length; i++)
85
+ {
86
+ if (!nonResetableCCs.has(i))
87
+ {
88
+ savedControllers[chan][i] = defaultControllerArray[i];
89
+ }
90
+ }
91
+ }
92
+
93
+ while (true)
94
+ {
95
+ // find the next event
96
+ let trackIndex = this._findFirstEventIndex();
97
+ let event = this.tracks[trackIndex][this.eventIndex[trackIndex]];
98
+ if (ticks !== undefined)
99
+ {
100
+ if (event.ticks >= ticks)
101
+ {
102
+ break;
103
+ }
104
+ }
105
+ else
106
+ {
107
+ if (this.playedTime >= time)
108
+ {
109
+ break;
110
+ }
111
+ }
112
+
113
+ // skip note ons
114
+ const info = getEvent(event.messageStatusByte);
115
+ // Keep in mind midi ports to determine the channel!
116
+ const channel = info.channel + (this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0);
117
+ switch (info.status)
118
+ {
119
+ // skip note messages
120
+ case messageTypes.noteOn:
121
+ // track portamento control as last note
122
+ if (savedControllers[channel] === undefined)
123
+ {
124
+ savedControllers[channel] = Array.from(defaultControllerArray);
125
+ }
126
+ savedControllers[channel][midiControllers.portamentoControl] = event.messageData[0];
127
+ break;
128
+
129
+ case messageTypes.noteOff:
130
+ break;
131
+
132
+ // skip pitch bend
133
+ case messageTypes.pitchBend:
134
+ pitchBends[channel] = event.messageData[1] << 7 | event.messageData[0];
135
+ break;
136
+
137
+ case messageTypes.programChange:
138
+ // empty tracks cannot program change
139
+ if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
140
+ {
141
+ break;
142
+ }
143
+ const p = programs[channel];
144
+ p.program = event.messageData[0];
145
+ p.actualBank = p.bank;
146
+ break;
147
+
148
+ case messageTypes.controllerChange:
149
+ // empty tracks cannot controller change
150
+ if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
151
+ {
152
+ break;
153
+ }
154
+ // do not skip data entries
155
+ const controllerNumber = event.messageData[0];
156
+ if (isCCNonSkippable(controllerNumber))
157
+ {
158
+ let ccV = event.messageData[1];
159
+ if (controllerNumber === midiControllers.bankSelect)
160
+ {
161
+ // add the bank to be saved
162
+ programs[channel].bank = ccV;
163
+ break;
164
+ }
165
+ else if (controllerNumber === midiControllers.resetAllControllers)
166
+ {
167
+ resetAllControlllers(channel);
168
+ }
169
+ if (this.sendMIDIMessages)
170
+ {
171
+ this.sendMIDICC(channel, controllerNumber, ccV);
172
+ }
173
+ else
174
+ {
175
+ this.synth.controllerChange(channel, controllerNumber, ccV);
176
+ }
177
+ }
178
+ else
179
+ {
180
+ if (savedControllers[channel] === undefined)
181
+ {
182
+ savedControllers[channel] = Array.from(defaultControllerArray);
183
+ }
184
+ savedControllers[channel][controllerNumber] = event.messageData[1];
185
+ }
186
+ break;
187
+
188
+ default:
189
+ this._processEvent(event, trackIndex);
190
+ break;
191
+ }
192
+
193
+ this.eventIndex[trackIndex]++;
194
+ // find the next event
195
+ trackIndex = this._findFirstEventIndex();
196
+ let nextEvent = this.tracks[trackIndex][this.eventIndex[trackIndex]];
197
+ if (nextEvent === undefined)
198
+ {
199
+ this.stop();
200
+ return false;
201
+ }
202
+ this.playedTime += this.oneTickToSeconds * (nextEvent.ticks - event.ticks);
203
+ }
204
+
205
+ // restoring saved controllers
206
+ if (this.sendMIDIMessages)
207
+ {
208
+ for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++)
209
+ {
210
+ // restore pitch bends
211
+ if (pitchBends[channelNumber] !== undefined)
212
+ {
213
+ this.sendMIDIPitchWheel(
214
+ channelNumber,
215
+ pitchBends[channelNumber] >> 7,
216
+ pitchBends[channelNumber] & 0x7F
217
+ );
218
+ }
219
+ if (savedControllers[channelNumber] !== undefined)
220
+ {
221
+ // every controller that has changed
222
+ savedControllers[channelNumber].forEach((value, index) =>
223
+ {
224
+ if (value !== defaultControllerArray[index] && !isCCNonSkippable(
225
+ index))
226
+ {
227
+ this.sendMIDICC(channelNumber, index, value);
228
+ }
229
+ });
230
+ }
231
+ // restore programs
232
+ if (programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0)
233
+ {
234
+ const bank = programs[channelNumber].actualBank;
235
+ this.sendMIDICC(channelNumber, midiControllers.bankSelect, bank);
236
+ this.sendMIDIProgramChange(channelNumber, programs[channelNumber].program);
237
+ }
238
+ }
239
+ }
240
+ else
241
+ {
242
+ // for all synth channels
243
+ for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++)
244
+ {
245
+ // restore pitch bends
246
+ if (pitchBends[channelNumber] !== undefined)
247
+ {
248
+ this.synth.pitchWheel(channelNumber, pitchBends[channelNumber] >> 7, pitchBends[channelNumber] & 0x7F);
249
+ }
250
+ if (savedControllers[channelNumber] !== undefined)
251
+ {
252
+ // every controller that has changed
253
+ savedControllers[channelNumber].forEach((value, index) =>
254
+ {
255
+ if (value !== defaultControllerArray[index] && !isCCNonSkippable(
256
+ index))
257
+ {
258
+ this.synth.controllerChange(
259
+ channelNumber,
260
+ index,
261
+ value
262
+ );
263
+ }
264
+ });
265
+ }
266
+ // restore programs
267
+ if (programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0)
268
+ {
269
+ const bank = programs[channelNumber].actualBank;
270
+ this.synth.controllerChange(channelNumber, midiControllers.bankSelect, bank);
271
+ this.synth.programChange(channelNumber, programs[channelNumber].program);
272
+ }
273
+ }
274
+ }
275
+ return true;
276
+ }
277
+
278
+ /**
279
+ * Starts the playback
280
+ * @param resetTime {boolean} If true, time is set to 0 s
281
+ * @this {SpessaSynthSequencer}
282
+ */
283
+ export function play(resetTime = false)
284
+ {
285
+ if (this.midiData === undefined)
286
+ {
287
+ return;
288
+ }
289
+
290
+ // reset the time if necessary
291
+ if (resetTime)
292
+ {
293
+ this.pausedTime = undefined;
294
+ this.currentTime = 0;
295
+ return;
296
+ }
297
+
298
+ if (this.currentTime >= this.duration)
299
+ {
300
+ this.pausedTime = undefined;
301
+ this.currentTime = 0;
302
+ return;
303
+ }
304
+
305
+ // unpause if paused
306
+ if (this.paused)
307
+ {
308
+ // adjust the start time
309
+ this._recalculateStartTime(this.pausedTime);
310
+ this.pausedTime = undefined;
311
+ }
312
+ if (!this.sendMIDIMessages)
313
+ {
314
+ this.playingNotes.forEach(n =>
315
+ {
316
+ this.synth.noteOn(n.channel, n.midiNote, n.velocity);
317
+ });
318
+ }
319
+ this.setProcessHandler();
320
+ }
321
+
322
+ /**
323
+ * @this {SpessaSynthSequencer}
324
+ * @param ticks {number}
325
+ */
326
+ export function setTimeTicks(ticks)
327
+ {
328
+ this.stop();
329
+ this.playingNotes = [];
330
+ this.pausedTime = undefined;
331
+ this?.onTimeChange?.(this.midiData.MIDIticksToSeconds(ticks));
332
+ const isNotFinished = this._playTo(0, ticks);
333
+ this._recalculateStartTime(this.playedTime);
334
+ if (!isNotFinished)
335
+ {
336
+ return;
337
+ }
338
+ this.play();
339
+ }
340
+
341
+ /**
342
+ * @param time
343
+ * @private
344
+ * @this {SpessaSynthSequencer}
345
+ */
346
+ export function _recalculateStartTime(time)
347
+ {
348
+ this.absoluteStartTime = this.synth.currentSynthTime - time / this._playbackRate;
349
+ }
@@ -0,0 +1,165 @@
1
+ import { getEvent, messageTypes } from "../midi/midi_message.js";
2
+ import { consoleColors } from "../utils/other.js";
3
+ import { SpessaSynthWarn } from "../utils/loggin.js";
4
+ import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
5
+
6
+ /**
7
+ * Processes a single event
8
+ * @param event {MIDIMessage}
9
+ * @param trackIndex {number}
10
+ * @this {SpessaSynthSequencer}
11
+ * @private
12
+ */
13
+ export function _processEvent(event, trackIndex)
14
+ {
15
+ if (this.sendMIDIMessages)
16
+ {
17
+ if (event.messageStatusByte >= 0x80)
18
+ {
19
+ this.sendMIDIMessage([event.messageStatusByte, ...event.messageData]);
20
+ return;
21
+ }
22
+ }
23
+ const statusByteData = getEvent(event.messageStatusByte);
24
+ const offset = this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0;
25
+ statusByteData.channel += offset;
26
+ // process the event
27
+ switch (statusByteData.status)
28
+ {
29
+ case messageTypes.noteOn:
30
+ const velocity = event.messageData[1];
31
+ if (velocity > 0)
32
+ {
33
+ this.synth.noteOn(statusByteData.channel, event.messageData[0], velocity);
34
+ this.playingNotes.push({
35
+ midiNote: event.messageData[0],
36
+ channel: statusByteData.channel,
37
+ velocity: velocity
38
+ });
39
+ }
40
+ else
41
+ {
42
+ this.synth.noteOff(statusByteData.channel, event.messageData[0]);
43
+ const toDelete = this.playingNotes.findIndex(n =>
44
+ n.midiNote === event.messageData[0] && n.channel === statusByteData.channel);
45
+ if (toDelete !== -1)
46
+ {
47
+ this.playingNotes.splice(toDelete, 1);
48
+ }
49
+ }
50
+ break;
51
+
52
+ case messageTypes.noteOff:
53
+ this.synth.noteOff(statusByteData.channel, event.messageData[0]);
54
+ const toDelete = this.playingNotes.findIndex(n =>
55
+ n.midiNote === event.messageData[0] && n.channel === statusByteData.channel);
56
+ if (toDelete !== -1)
57
+ {
58
+ this.playingNotes.splice(toDelete, 1);
59
+ }
60
+ break;
61
+
62
+ case messageTypes.pitchBend:
63
+ this.synth.pitchWheel(statusByteData.channel, event.messageData[1], event.messageData[0]);
64
+ break;
65
+
66
+ case messageTypes.controllerChange:
67
+ // empty tracks cannot cc change
68
+ if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
69
+ {
70
+ return;
71
+ }
72
+ this.synth.controllerChange(statusByteData.channel, event.messageData[0], event.messageData[1]);
73
+ break;
74
+
75
+ case messageTypes.programChange:
76
+ // empty tracks cannot program change
77
+ if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
78
+ {
79
+ return;
80
+ }
81
+ this.synth.programChange(statusByteData.channel, event.messageData[0]);
82
+ break;
83
+
84
+ case messageTypes.polyPressure:
85
+ this.synth.polyPressure(statusByteData.channel, event.messageData[0], event.messageData[1]);
86
+ break;
87
+
88
+ case messageTypes.channelPressure:
89
+ this.synth.channelPressure(statusByteData.channel, event.messageData[0]);
90
+ break;
91
+
92
+ case messageTypes.systemExclusive:
93
+ this.synth.systemExclusive(event.messageData, offset);
94
+ break;
95
+
96
+ case messageTypes.setTempo:
97
+ event.messageData.currentIndex = 0;
98
+ let tempoBPM = 60000000 / readBytesAsUintBigEndian(event.messageData, 3);
99
+ this.oneTickToSeconds = 60 / (tempoBPM * this.midiData.timeDivision);
100
+ if (this.oneTickToSeconds === 0)
101
+ {
102
+ this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
103
+ SpessaSynthWarn("invalid tempo! falling back to 120 BPM");
104
+ tempoBPM = 120;
105
+ }
106
+ break;
107
+
108
+ // recognized but ignored
109
+ case messageTypes.timeSignature:
110
+ case messageTypes.endOfTrack:
111
+ case messageTypes.midiChannelPrefix:
112
+ case messageTypes.songPosition:
113
+ case messageTypes.activeSensing:
114
+ case messageTypes.keySignature:
115
+ case messageTypes.sequenceNumber:
116
+ case messageTypes.sequenceSpecific:
117
+ case messageTypes.text:
118
+ case messageTypes.lyric:
119
+ case messageTypes.copyright:
120
+ case messageTypes.trackName:
121
+ case messageTypes.marker:
122
+ case messageTypes.cuePoint:
123
+ case messageTypes.instrumentName:
124
+ case messageTypes.programName:
125
+ break;
126
+
127
+
128
+ case messageTypes.midiPort:
129
+ this.assignMIDIPort(trackIndex, event.messageData[0]);
130
+ break;
131
+
132
+ case messageTypes.reset:
133
+ this.synth.stopAllChannels();
134
+ this.synth.resetAllControllers();
135
+ break;
136
+
137
+ default:
138
+ SpessaSynthWarn(
139
+ `%cUnrecognized Event: %c${event.messageStatusByte}%c status byte: %c${Object.keys(
140
+ messageTypes).find(k => messageTypes[k] === statusByteData.status)}`,
141
+ consoleColors.warn,
142
+ consoleColors.unrecognized,
143
+ consoleColors.warn,
144
+ consoleColors.value
145
+ );
146
+ break;
147
+ }
148
+ if (statusByteData.status >= 0 && statusByteData.status < 0x80)
149
+ {
150
+ this?.onMetaEvent?.(event, trackIndex);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Adds 16 channels to the synth
156
+ * @this {SpessaSynthSequencer}
157
+ * @private
158
+ */
159
+ export function _addNewMidiPort()
160
+ {
161
+ for (let i = 0; i < 16; i++)
162
+ {
163
+ this.synth.createWorkletChannel(true);
164
+ }
165
+ }