spessasynth_lib 0.0.1

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 (154) hide show
  1. package/.idea/modules.xml +8 -0
  2. package/.idea/spessasynth_lib.iml +12 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/copy_version.sh +38 -0
  5. package/index.js +73 -0
  6. package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  7. package/package/@types/index.d.ts +34 -0
  8. package/package/@types/midi_handler/midi_handler.d.ts +39 -0
  9. package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
  10. package/package/@types/midi_parser/midi_data.d.ts +95 -0
  11. package/package/@types/midi_parser/midi_editor.d.ts +45 -0
  12. package/package/@types/midi_parser/midi_loader.d.ts +100 -0
  13. package/package/@types/midi_parser/midi_message.d.ts +154 -0
  14. package/package/@types/midi_parser/midi_writer.d.ts +6 -0
  15. package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  16. package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  17. package/package/@types/sequencer/sequencer.d.ts +180 -0
  18. package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  19. package/package/@types/soundfont/read/generators.d.ts +98 -0
  20. package/package/@types/soundfont/read/instruments.d.ts +50 -0
  21. package/package/@types/soundfont/read/modulators.d.ts +73 -0
  22. package/package/@types/soundfont/read/presets.d.ts +87 -0
  23. package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  24. package/package/@types/soundfont/read/samples.d.ts +134 -0
  25. package/package/@types/soundfont/read/zones.d.ts +141 -0
  26. package/package/@types/soundfont/soundfont.d.ts +76 -0
  27. package/package/@types/soundfont/write/ibag.d.ts +6 -0
  28. package/package/@types/soundfont/write/igen.d.ts +6 -0
  29. package/package/@types/soundfont/write/imod.d.ts +6 -0
  30. package/package/@types/soundfont/write/inst.d.ts +6 -0
  31. package/package/@types/soundfont/write/pbag.d.ts +6 -0
  32. package/package/@types/soundfont/write/pgen.d.ts +6 -0
  33. package/package/@types/soundfont/write/phdr.d.ts +6 -0
  34. package/package/@types/soundfont/write/pmod.d.ts +6 -0
  35. package/package/@types/soundfont/write/sdta.d.ts +11 -0
  36. package/package/@types/soundfont/write/shdr.d.ts +8 -0
  37. package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  38. package/package/@types/soundfont/write/write.d.ts +21 -0
  39. package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  40. package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  41. package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  42. package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  43. package/package/@types/synthetizer/synthetizer.d.ts +294 -0
  44. package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
  45. package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  46. package/package/@types/synthetizer/worklet_url.d.ts +5 -0
  47. package/package/@types/utils/buffer_to_wav.d.ts +8 -0
  48. package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  49. package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  50. package/package/@types/utils/byte_functions/string.d.ts +22 -0
  51. package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  52. package/package/@types/utils/indexed_array.d.ts +21 -0
  53. package/package/@types/utils/loggin.d.ts +26 -0
  54. package/package/@types/utils/other.d.ts +32 -0
  55. package/package/LICENSE +26 -0
  56. package/package/README.md +84 -0
  57. package/package/externals/NOTICE +9 -0
  58. package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  59. package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  60. package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  61. package/package/externals/stbvorbis_sync/LICENSE +202 -0
  62. package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  63. package/package/index.js +73 -0
  64. package/package/midi_handler/README.md +3 -0
  65. package/package/midi_handler/midi_handler.js +118 -0
  66. package/package/midi_handler/web_midi_link.js +41 -0
  67. package/package/midi_parser/README.md +3 -0
  68. package/package/midi_parser/midi_data.js +121 -0
  69. package/package/midi_parser/midi_editor.js +557 -0
  70. package/package/midi_parser/midi_loader.js +502 -0
  71. package/package/midi_parser/midi_message.js +234 -0
  72. package/package/midi_parser/midi_writer.js +95 -0
  73. package/package/midi_parser/rmidi_writer.js +271 -0
  74. package/package/midi_parser/used_keys_loaded.js +172 -0
  75. package/package/package.json +43 -0
  76. package/package/sequencer/README.md +23 -0
  77. package/package/sequencer/sequencer.js +439 -0
  78. package/package/sequencer/worklet_sequencer/events.js +92 -0
  79. package/package/sequencer/worklet_sequencer/play.js +309 -0
  80. package/package/sequencer/worklet_sequencer/process_event.js +167 -0
  81. package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
  82. package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  83. package/package/sequencer/worklet_sequencer/song_control.js +193 -0
  84. package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  85. package/package/soundfont/README.md +8 -0
  86. package/package/soundfont/read/generators.js +212 -0
  87. package/package/soundfont/read/instruments.js +125 -0
  88. package/package/soundfont/read/modulators.js +249 -0
  89. package/package/soundfont/read/presets.js +300 -0
  90. package/package/soundfont/read/riff_chunk.js +81 -0
  91. package/package/soundfont/read/samples.js +398 -0
  92. package/package/soundfont/read/zones.js +310 -0
  93. package/package/soundfont/soundfont.js +357 -0
  94. package/package/soundfont/write/ibag.js +39 -0
  95. package/package/soundfont/write/igen.js +75 -0
  96. package/package/soundfont/write/imod.js +46 -0
  97. package/package/soundfont/write/inst.js +34 -0
  98. package/package/soundfont/write/pbag.js +39 -0
  99. package/package/soundfont/write/pgen.js +77 -0
  100. package/package/soundfont/write/phdr.js +42 -0
  101. package/package/soundfont/write/pmod.js +46 -0
  102. package/package/soundfont/write/sdta.js +72 -0
  103. package/package/soundfont/write/shdr.js +54 -0
  104. package/package/soundfont/write/soundfont_trimmer.js +169 -0
  105. package/package/soundfont/write/write.js +180 -0
  106. package/package/synthetizer/README.md +6 -0
  107. package/package/synthetizer/audio_effects/effects_config.js +21 -0
  108. package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  109. package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  110. package/package/synthetizer/audio_effects/reverb.js +24 -0
  111. package/package/synthetizer/synth_event_handler.js +156 -0
  112. package/package/synthetizer/synthetizer.js +766 -0
  113. package/package/synthetizer/worklet_processor.min.js +13 -0
  114. package/package/synthetizer/worklet_system/README.md +6 -0
  115. package/package/synthetizer/worklet_system/main_processor.js +363 -0
  116. package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
  117. package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  118. package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
  119. package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
  120. package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  121. package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  122. package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  123. package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  124. package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  125. package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  126. package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  127. package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  128. package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  129. package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  130. package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  131. package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
  132. package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  133. package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  134. package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  135. package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  136. package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  137. package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  138. package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  139. package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  140. package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  141. package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  142. package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  143. package/package/synthetizer/worklet_url.js +5 -0
  144. package/package/utils/README.md +4 -0
  145. package/package/utils/buffer_to_wav.js +101 -0
  146. package/package/utils/byte_functions/big_endian.js +28 -0
  147. package/package/utils/byte_functions/little_endian.js +74 -0
  148. package/package/utils/byte_functions/string.js +97 -0
  149. package/package/utils/byte_functions/variable_length_quantity.js +37 -0
  150. package/package/utils/encode_vorbis.js +30 -0
  151. package/package/utils/indexed_array.js +41 -0
  152. package/package/utils/loggin.js +79 -0
  153. package/package/utils/other.js +54 -0
  154. package/package.json +43 -0
@@ -0,0 +1,92 @@
1
+ import { returnMessageType } from '../../synthetizer/worklet_system/message_protocol/worklet_message.js'
2
+ import { WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from './sequencer_message.js'
3
+
4
+ /**
5
+ * @param messageType {WorkletSequencerMessageType}
6
+ * @param messageData {any}
7
+ * @this {WorkletSequencer}
8
+ */
9
+ export function processMessage(messageType, messageData)
10
+ {
11
+ switch (messageType)
12
+ {
13
+ default:
14
+ break;
15
+
16
+ case WorkletSequencerMessageType.loadNewSongList:
17
+ this.loadNewSongList(messageData);
18
+ break;
19
+
20
+ case WorkletSequencerMessageType.pause:
21
+ this.pause();
22
+ break;
23
+
24
+ case WorkletSequencerMessageType.play:
25
+ this.play(messageData);
26
+ break;
27
+
28
+ case WorkletSequencerMessageType.stop:
29
+ this.stop();
30
+ break;
31
+
32
+ case WorkletSequencerMessageType.setTime:
33
+ this.currentTime = messageData;
34
+ break;
35
+
36
+ case WorkletSequencerMessageType.changeMIDIMessageSending:
37
+ this.sendMIDIMessages = messageData;
38
+ break;
39
+
40
+ case WorkletSequencerMessageType.setPlaybackRate:
41
+ this.playbackRate = messageData;
42
+ break;
43
+
44
+ case WorkletSequencerMessageType.setLoop:
45
+ this.loop = messageData;
46
+ break;
47
+
48
+ case WorkletSequencerMessageType.changeSong:
49
+ if (messageData)
50
+ {
51
+ this.nextSong();
52
+ }
53
+ else
54
+ {
55
+ this.previousSong();
56
+ }
57
+ break;
58
+
59
+ case WorkletSequencerMessageType.getMIDI:
60
+ this.post(WorkletSequencerReturnMessageType.getMIDI, this.midiData);
61
+ }
62
+ }
63
+
64
+ /**
65
+ *
66
+ * @param messageType {WorkletSequencerReturnMessageType}
67
+ * @param messageData {any}
68
+ * @this {WorkletSequencer}
69
+ */
70
+ export function post(messageType, messageData = undefined)
71
+ {
72
+ if(!this.synth.enableEventSystem)
73
+ {
74
+ return;
75
+ }
76
+ this.synth.post({
77
+ messageType: returnMessageType.sequencerSpecific,
78
+ messageData: {
79
+ messageType: messageType,
80
+ messageData: messageData
81
+ }
82
+ })
83
+ }
84
+
85
+ /**
86
+ * @param message {number[]}
87
+ * @this {WorkletSequencer}
88
+ */
89
+ export function sendMIDIMessage(message)
90
+ {
91
+ this.post(WorkletSequencerReturnMessageType.midiEvent, message);
92
+ }
@@ -0,0 +1,309 @@
1
+ import { getEvent, messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
2
+ import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
3
+ import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
4
+
5
+
6
+ // an array with preset default values
7
+ const defaultControllerArray = new Int16Array(127);
8
+ // default values
9
+ defaultControllerArray[midiControllers.mainVolume] = 100;
10
+ defaultControllerArray[midiControllers.expressionController] = 127;
11
+ defaultControllerArray[midiControllers.pan] = 64;
12
+ defaultControllerArray[midiControllers.releaseTime] = 64;
13
+ defaultControllerArray[midiControllers.brightness] = 64;
14
+ defaultControllerArray[midiControllers.effects1Depth] = 40;
15
+
16
+ /**
17
+ * plays from start to the target time, excluding note messages (to get the synth to the correct state)
18
+ * @private
19
+ * @param time {number} in seconds
20
+ * @param ticks {number} optional MIDI ticks, when given is used instead of time
21
+ * @returns {boolean} true if the midi file is not finished
22
+ * @this {WorkletSequencer}
23
+ */
24
+ export function _playTo(time, ticks = undefined)
25
+ {
26
+ this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
27
+ // reset
28
+ this.synth.resetAllControllers();
29
+ if(this.sendMIDIMessages)
30
+ {
31
+ this.sendMIDIMessage([messageTypes.reset]);
32
+ for(let ch = 0; ch < MIDI_CHANNEL_COUNT; ch++)
33
+ {
34
+ this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.resetAllControllers, 0]);
35
+ }
36
+ }
37
+ this._resetTimers()
38
+
39
+ const channelsToSave = this.synth.workletProcessorChannels.length;
40
+ /**
41
+ * save pitch bends here and send them only after
42
+ * @type {number[]}
43
+ */
44
+ const pitchBends = Array(channelsToSave).fill(8192);
45
+
46
+ /**
47
+ * Save programs here and send them only after
48
+ * @type {number[]}
49
+ */
50
+ const programs = Array(channelsToSave).fill(-1 );
51
+
52
+ /**
53
+ * Save controllers here and send them only after
54
+ * @type {number[][]}
55
+ */
56
+ const savedControllers = [];
57
+ for (let i = 0; i < channelsToSave; i++)
58
+ {
59
+ savedControllers.push(Array.from(defaultControllerArray));
60
+ }
61
+
62
+ while(true)
63
+ {
64
+ // find next event
65
+ let trackIndex = this._findFirstEventIndex();
66
+ let event = this.tracks[trackIndex][this.eventIndex[trackIndex]];
67
+ if(ticks !== undefined)
68
+ {
69
+ if(event.ticks >= ticks)
70
+ {
71
+ break;
72
+ }
73
+ }
74
+ else
75
+ {
76
+ if(this.playedTime >= time)
77
+ {
78
+ break;
79
+ }
80
+ }
81
+
82
+ // skip note ons
83
+ const info = getEvent(event.messageStatusByte);
84
+ // Keep in mind midi ports to determine channel!!
85
+ const channel = info.channel + (this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0);
86
+ switch(info.status)
87
+ {
88
+ // skip note messages
89
+ case messageTypes.noteOn:
90
+ case messageTypes.noteOff:
91
+ case messageTypes.keySignature:
92
+ break;
93
+
94
+ // skip pitch bend
95
+ case messageTypes.pitchBend:
96
+ pitchBends[channel] = event.messageData[1] << 7 | event.messageData[0];
97
+ break;
98
+
99
+ case messageTypes.programChange:
100
+ programs[channel] = event.messageData[0];
101
+ break;
102
+
103
+ case messageTypes.controllerChange:
104
+ // do not skip data entries
105
+ const controllerNumber = event.messageData[0];
106
+ if(
107
+ controllerNumber === midiControllers.dataDecrement ||
108
+ controllerNumber === midiControllers.dataIncrement ||
109
+ controllerNumber === midiControllers.dataEntryMsb ||
110
+ controllerNumber === midiControllers.dataDecrement ||
111
+ controllerNumber === midiControllers.lsbForControl6DataEntry ||
112
+ controllerNumber === midiControllers.RPNLsb ||
113
+ controllerNumber === midiControllers.RPNMsb ||
114
+ controllerNumber === midiControllers.NRPNLsb ||
115
+ controllerNumber === midiControllers.NRPNMsb ||
116
+ controllerNumber === midiControllers.bankSelect ||
117
+ controllerNumber === midiControllers.lsbForControl0BankSelect||
118
+ controllerNumber === midiControllers.resetAllControllers
119
+ )
120
+ {
121
+ if(this.sendMIDIMessages)
122
+ {
123
+ this.sendMIDIMessage([messageTypes.controllerChange | (channel % 16), controllerNumber, event.messageData[1]])
124
+ }
125
+ else
126
+ {
127
+ let ccV = event.messageData[1];
128
+ if(this.midiData.embeddedSoundFont !== undefined && controllerNumber === midiControllers.bankSelect)
129
+ {
130
+ // special case if the RMID is embedded: subtract 1 from bank. See wiki About-RMIDI
131
+ ccV--;
132
+ }
133
+ this.synth.controllerChange(channel, controllerNumber, ccV);
134
+ }
135
+ }
136
+ else
137
+ {
138
+ if(savedControllers[channel] === undefined)
139
+ {
140
+ savedControllers[channel] = Array.from(defaultControllerArray);
141
+ }
142
+ savedControllers[channel][controllerNumber] = event.messageData[1];
143
+ }
144
+ break;
145
+
146
+ default:
147
+ this._processEvent(event, trackIndex);
148
+ break;
149
+ }
150
+
151
+ this.eventIndex[trackIndex]++;
152
+ // find next event
153
+ trackIndex = this._findFirstEventIndex();
154
+ let nextEvent = this.tracks[trackIndex][this.eventIndex[trackIndex]];
155
+ if(nextEvent === undefined)
156
+ {
157
+ this.stop();
158
+ return false;
159
+ }
160
+ this.playedTime += this.oneTickToSeconds * (nextEvent.ticks - event.ticks);
161
+ }
162
+
163
+ // restoring saved controllers
164
+ if(this.sendMIDIMessages)
165
+ {
166
+ // for all 16 channels
167
+ for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) {
168
+ // send saved pitch bend
169
+ this.sendMIDIMessage([messageTypes.pitchBend | (channelNumber % 16), pitchBends[channelNumber] & 0x7F, pitchBends[channelNumber] >> 7]);
170
+
171
+ // every controller that has changed
172
+ savedControllers[channelNumber].forEach((value, index) => {
173
+ if(value !== defaultControllerArray[index])
174
+ {
175
+ this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), index, value])
176
+ }
177
+ });
178
+
179
+ // restore programs
180
+ if(programs[channelNumber] !== 0)
181
+ {
182
+ this.sendMIDIMessage([messageTypes.programChange | (channelNumber % 16), programs[channelNumber]]);
183
+ }
184
+ }
185
+ }
186
+ else
187
+ {
188
+ // for all synth channels
189
+ for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++)
190
+ {
191
+ // restore pitch bends
192
+ if(pitchBends[channelNumber] !== undefined)
193
+ {
194
+ this.synth.pitchWheel(channelNumber, pitchBends[channelNumber] >> 7, pitchBends[channelNumber] & 0x7F);
195
+ }
196
+ if(savedControllers[channelNumber] !== undefined)
197
+ {
198
+ // every controller that has changed
199
+ savedControllers[channelNumber].forEach((value, index) => {
200
+ if(value !== defaultControllerArray[index])
201
+ {
202
+ this.synth.controllerChange(channelNumber, index, value);
203
+ }
204
+ })
205
+ }
206
+ // restore programs
207
+ if(programs[channelNumber] !== -1)
208
+ {
209
+ this.synth.programChange(channelNumber, programs[channelNumber]);
210
+ }
211
+ }
212
+ }
213
+ return true;
214
+ }
215
+
216
+ /**
217
+ * Starts the playback
218
+ * @param resetTime {boolean} If true, time is set to 0s
219
+ * @this {WorkletSequencer}
220
+ */
221
+ export function play(resetTime = false)
222
+ {
223
+ if(this.midiData === undefined)
224
+ {
225
+ return;
226
+ }
227
+
228
+ // reset the time if necesarry
229
+ if(resetTime)
230
+ {
231
+ this.currentTime = 0;
232
+ return;
233
+ }
234
+
235
+ if(this.currentTime >= this.duration)
236
+ {
237
+ this.currentTime = 0;
238
+ return;
239
+ }
240
+
241
+ // unpause if paused
242
+ if(this.paused)
243
+ {
244
+ // adjust the start time
245
+ this._recalculateStartTime(this.pausedTime)
246
+ this.pausedTime = undefined;
247
+ }
248
+ if(!this.sendMIDIMessages)
249
+ {
250
+ const time = this.currentTime;
251
+ this.playingNotes.forEach(n => {
252
+ const timeOffset = n.startTime - time;
253
+ this.synth.noteOn(n.channel, n.midiNote, n.velocity, false, true, currentTime + timeOffset);
254
+ });
255
+ }
256
+ this.setProcessHandler();
257
+ }
258
+
259
+ /**
260
+ * Coverts ticks to time in seconds
261
+ * @param changes {{tempo: number, ticks: number}[]}
262
+ * @param ticks {number}
263
+ * @param division {number}
264
+ * @returns {number}
265
+ */
266
+ export function ticksToSeconds(changes, ticks, division)
267
+ {
268
+ if (ticks <= 0) {
269
+ return 0;
270
+ }
271
+
272
+ // find the last tempo change that has occured
273
+ let tempo = changes.find(v => v.ticks < ticks);
274
+
275
+ let timeSinceLastTempo = ticks - tempo.ticks;
276
+ return ticksToSeconds(changes, ticks - timeSinceLastTempo, division) + (timeSinceLastTempo * 60) / (tempo.tempo * division);
277
+ }
278
+
279
+ /**
280
+ * @this {WorkletSequencer}
281
+ * @param ticks {number}
282
+ */
283
+ export function setTimeTicks(ticks)
284
+ {
285
+ this.stop();
286
+ this.playingNotes = [];
287
+ this.pausedTime = undefined;
288
+ this.post(
289
+ WorkletSequencerReturnMessageType.timeChange,
290
+ currentTime - ticksToSeconds(this.midiData.tempoChanges, ticks, this.midiData.timeDivision)
291
+ );
292
+ const isNotFinished = this._playTo(0, ticks);
293
+ this._recalculateStartTime(this.playedTime);
294
+ if(!isNotFinished)
295
+ {
296
+ return;
297
+ }
298
+ this.play();
299
+ }
300
+
301
+ /**
302
+ * @param time
303
+ * @private
304
+ * @this {WorkletSequencer}
305
+ */
306
+ export function _recalculateStartTime(time)
307
+ {
308
+ this.absoluteStartTime = currentTime - time / this._playbackRate;
309
+ }
@@ -0,0 +1,167 @@
1
+ import { getEvent, messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
2
+ import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
3
+ import { consoleColors } from '../../utils/other.js'
4
+ import { SpessaSynthWarn } from '../../utils/loggin.js'
5
+ import { readBytesAsUintBigEndian } from '../../utils/byte_functions/big_endian.js'
6
+ import { DEFAULT_PERCUSSION } from '../../synthetizer/synthetizer.js'
7
+
8
+ /**
9
+ * Processes a single event
10
+ * @param event {MidiMessage}
11
+ * @param trackIndex {number}
12
+ * @this {WorkletSequencer}
13
+ * @private
14
+ */
15
+ export function _processEvent(event, trackIndex)
16
+ {
17
+ if(this.ignoreEvents) return;
18
+ if(this.sendMIDIMessages)
19
+ {
20
+ if(event.messageStatusByte >= 0x80)
21
+ {
22
+ this.sendMIDIMessage([event.messageStatusByte, ...event.messageData]);
23
+ return;
24
+ }
25
+ }
26
+ const statusByteData = getEvent(event.messageStatusByte);
27
+ const offset = this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0
28
+ statusByteData.channel += offset;
29
+ // process the event
30
+ switch (statusByteData.status) {
31
+ case messageTypes.noteOn:
32
+ const velocity = event.messageData[1];
33
+ if(velocity > 0)
34
+ {
35
+ this.synth.noteOn(statusByteData.channel, event.messageData[0], velocity);
36
+ this.playingNotes.push({
37
+ midiNote: event.messageData[0],
38
+ channel: statusByteData.channel,
39
+ velocity: velocity,
40
+ startTime: this.currentTime
41
+ });
42
+ }
43
+ else
44
+ {
45
+ this.synth.noteOff(statusByteData.channel, event.messageData[0]);
46
+ const toDelete = this.playingNotes.findIndex(n =>
47
+ n.midiNote === event.messageData[0] && n.channel === statusByteData.channel);
48
+ if(toDelete !== -1)
49
+ {
50
+ this.playingNotes.splice(toDelete, 1);
51
+ }
52
+ }
53
+ break;
54
+
55
+ case messageTypes.noteOff:
56
+ this.synth.noteOff(statusByteData.channel, event.messageData[0]);
57
+ const toDelete = this.playingNotes.findIndex(n =>
58
+ n.midiNote === event.messageData[0] && n.channel === statusByteData.channel);
59
+ if(toDelete !== -1)
60
+ {
61
+ this.playingNotes.splice(toDelete, 1);
62
+ }
63
+ break;
64
+
65
+ case messageTypes.pitchBend:
66
+ this.synth.pitchWheel(statusByteData.channel, event.messageData[1], event.messageData[0]);
67
+ break;
68
+
69
+ case messageTypes.controllerChange:
70
+ // special case if the RMID is embedded: subtract 1 from bank. See wiki About-RMIDI
71
+ let v = event.messageData[1];
72
+ if(this.midiData.embeddedSoundFont && event.messageData[0] === midiControllers.bankSelect)
73
+ {
74
+ v--;
75
+ }
76
+ this.synth.controllerChange(statusByteData.channel, event.messageData[0], v);
77
+ break;
78
+
79
+ case messageTypes.programChange:
80
+ this.synth.programChange(statusByteData.channel, event.messageData[0]);
81
+ break;
82
+
83
+ case messageTypes.polyPressure:
84
+ this.synth.polyPressure(statusByteData.channel, event.messageData[0], event.messageData[1]);
85
+ break;
86
+
87
+ case messageTypes.channelPressure:
88
+ this.synth.channelPressure(statusByteData.channel, event.messageData[0]);
89
+ break;
90
+
91
+ case messageTypes.systemExclusive:
92
+ this.synth.systemExclusive(event.messageData, offset);
93
+ break;
94
+
95
+ case messageTypes.setTempo:
96
+ this.oneTickToSeconds = 60 / (getTempo(event) * this.midiData.timeDivision);
97
+ if(this.oneTickToSeconds === 0)
98
+ {
99
+ this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
100
+ SpessaSynthWarn("invalid tempo! falling back to 120 BPM");
101
+ }
102
+ break;
103
+
104
+ // recongized but ignored
105
+ case messageTypes.timeSignature:
106
+ case messageTypes.endOfTrack:
107
+ case messageTypes.midiChannelPrefix:
108
+ case messageTypes.songPosition:
109
+ case messageTypes.activeSensing:
110
+ case messageTypes.keySignature:
111
+ break;
112
+
113
+ case messageTypes.text:
114
+ case messageTypes.lyric:
115
+ case messageTypes.copyright:
116
+ case messageTypes.trackName:
117
+ case messageTypes.marker:
118
+ case messageTypes.cuePoint:
119
+ case messageTypes.instrumentName:
120
+ this.post(WorkletSequencerReturnMessageType.textEvent, [event.messageData, statusByteData.status])
121
+ break;
122
+
123
+ case messageTypes.midiPort:
124
+ this.assignMIDIPort(trackIndex, event.messageData[0]);
125
+ break;
126
+
127
+ case messageTypes.reset:
128
+ this.synth.stopAllChannels();
129
+ this.synth.resetAllControllers();
130
+ break;
131
+
132
+ default:
133
+ SpessaSynthWarn(`%cUnrecognized Event: %c${event.messageStatusByte}%c status byte: %c${Object.keys(messageTypes).find(k => messageTypes[k] === statusByteData.status)}`,
134
+ consoleColors.warn,
135
+ consoleColors.unrecognized,
136
+ consoleColors.warn,
137
+ consoleColors.value);
138
+ break;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Adds 16 channels to the synth
144
+ * @this {WorkletSequencer}
145
+ * @private
146
+ */
147
+ export function _addNewMidiPort()
148
+ {
149
+ for (let i = 0; i < 16; i++) {
150
+ this.synth.createWorkletChannel(true);
151
+ if(i === DEFAULT_PERCUSSION)
152
+ {
153
+ this.synth.setDrums(this.synth.workletProcessorChannels.length - 1, true);
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * gets tempo from the midi message
160
+ * @param event {MidiMessage}
161
+ * @return {number} the tempo in bpm
162
+ */
163
+ function getTempo(event)
164
+ {
165
+ event.messageData.currentIndex = 0;
166
+ return 60000000 / readBytesAsUintBigEndian(event.messageData, 3);
167
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Processes a single tick
3
+ * @private
4
+ * @this {WorkletSequencer}
5
+ */
6
+ export function _processTick()
7
+ {
8
+ let current = this.currentTime;
9
+ while(this.playedTime < current)
10
+ {
11
+ // find next event
12
+ let trackIndex = this._findFirstEventIndex();
13
+ let event = this.tracks[trackIndex][this.eventIndex[trackIndex]];
14
+ this._processEvent(event, trackIndex);
15
+
16
+ this.eventIndex[trackIndex]++;
17
+
18
+ // find next event
19
+ trackIndex = this._findFirstEventIndex();
20
+ if(this.tracks[trackIndex].length <= this.eventIndex[trackIndex])
21
+ {
22
+ // song has ended
23
+ if(this.loop)
24
+ {
25
+ this.setTimeTicks(this.midiData.loop.start);
26
+ return;
27
+ }
28
+ this.eventIndex[trackIndex]--;
29
+ this.pause(true);
30
+ if(this.songs.length > 1)
31
+ {
32
+ this.nextSong();
33
+ }
34
+ return;
35
+ }
36
+ let eventNext = this.tracks[trackIndex][this.eventIndex[trackIndex]];
37
+ this.playedTime += this.oneTickToSeconds * (eventNext.ticks - event.ticks);
38
+
39
+ // loop
40
+ if((this.midiData.loop.end <= event.ticks) && this.loop)
41
+ {
42
+ this.setTimeTicks(this.midiData.loop.start);
43
+ return;
44
+ }
45
+ // if song has ended
46
+ else if(current >= this.duration)
47
+ {
48
+ if(this.loop)
49
+ {
50
+ this.setTimeTicks(this.midiData.loop.start);
51
+ return;
52
+ }
53
+ this.eventIndex[trackIndex]--;
54
+ this.pause(true);
55
+ if(this.songs.length > 1)
56
+ {
57
+ this.nextSong();
58
+ }
59
+ return;
60
+ }
61
+ }
62
+ }
63
+
64
+
65
+ /**
66
+ * @returns {number} the index of the first to the current played time
67
+ * @this {WorkletSequencer}
68
+ */
69
+ export function _findFirstEventIndex()
70
+ {
71
+ let index = 0;
72
+ let ticks = Infinity;
73
+ this.tracks.forEach((track, i) => {
74
+ if(this.eventIndex[i] >= track.length)
75
+ {
76
+ return;
77
+ }
78
+ if(track[this.eventIndex[i]].ticks < ticks)
79
+ {
80
+ index = i;
81
+ ticks = track[this.eventIndex[i]].ticks;
82
+ }
83
+ });
84
+ return index;
85
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @enum {number}
3
+ * @property {number} loadNewSongList - 0 -> [...song<MIDI>]
4
+ * @property {number} pause - 1 -> isFinished<boolean>
5
+ * @property {number} stop - 2 -> (no data)
6
+ * @property {number} play - 3 -> resetTime<boolean>
7
+ * @property {number} setTime - 4 -> time<number>
8
+ * @property {number} changeMIDIMessageSending - 5 -> sendMIDIMessages<boolean>
9
+ * @property {number} setPlaybackRate - 6 -> playbackRate<number>
10
+ * @property {number} setLoop - 7 -> loop<boolean>
11
+ * @property {number} changeSong - 8 -> goForwards<boolean> if true, next song, if false, previous
12
+ * @property {number} getMIDI - 9 -> (no data)
13
+ */
14
+ export const WorkletSequencerMessageType = {
15
+ loadNewSongList: 0,
16
+ pause: 1,
17
+ stop: 2,
18
+ play: 3,
19
+ setTime: 4,
20
+ changeMIDIMessageSending: 5,
21
+ setPlaybackRate: 6,
22
+ setLoop: 7,
23
+ changeSong: 8,
24
+ getMIDI: 9
25
+ }
26
+
27
+ /**
28
+ *
29
+ * @enum {number}
30
+ */
31
+ export const WorkletSequencerReturnMessageType = {
32
+ midiEvent: 0, // [...midiEventBytes<number>]
33
+ songChange: 1, // [midiData<MidiData>, songIndex<number>]
34
+ textEvent: 2, // [messageData<number[]>, statusByte<number]
35
+ timeChange: 3, // newAbsoluteTime<number>
36
+ pause: 4, // no data
37
+ getMIDI: 5, // midiData<MIDI>
38
+ midiError: 6, // errorMSG<string>
39
+ }