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,172 @@
1
+ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from '../utils/loggin.js'
2
+ import { consoleColors } from '../utils/other.js'
3
+ import { DEFAULT_PERCUSSION } from '../synthetizer/synthetizer.js'
4
+ import { messageTypes, midiControllers } from './midi_message.js'
5
+
6
+ /**
7
+ * @param mid {MIDI}
8
+ * @param soundfont {SoundFont2}
9
+ */
10
+ export function getUsedProgramsAndKeys(mid, soundfont)
11
+ {
12
+ SpessaSynthGroupCollapsed("%cSearching for all used programs and keys...",
13
+ consoleColors.info);
14
+ // find every bank:program combo and every key:velocity for each. Make sure to care about ports and drums
15
+ const channelsAmount = 16 + Math.max.apply(undefined, mid.midiPorts) * 16;
16
+ /**
17
+ *
18
+ * @type {{program: number, bank: number, drums: boolean, string: string}[]}
19
+ */
20
+ const channelPresets = [];
21
+ for (let i = 0; i < channelsAmount; i++) {
22
+ const bank = i % 16 === DEFAULT_PERCUSSION ? 128 : 0;
23
+ channelPresets.push({
24
+ program: 0,
25
+ bank: bank,
26
+ drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
27
+ string: `${bank}:0`
28
+ });
29
+ }
30
+
31
+ function updateString(ch)
32
+ {
33
+ // check if this exists in the soundfont
34
+ let exists = soundfont.getPreset(ch.bank, ch.program);
35
+ if(exists.bank !== ch.bank && mid.embeddedSoundFont)
36
+ {
37
+ // maybe it doesn't exists becase RMIDI has a bank shift?
38
+ exists = soundfont.getPreset(ch.bank - 1, ch.program);
39
+ }
40
+ ch.bank = exists.bank;
41
+ ch.program = exists.program;
42
+ ch.string = ch.bank + ":" + ch.program;
43
+ if(!usedProgramsAndKeys[ch.string])
44
+ {
45
+ SpessaSynthInfo(`%cDetected a new preset: %c${ch.string}`,
46
+ consoleColors.info,
47
+ consoleColors.recognized);
48
+ usedProgramsAndKeys[ch.string] = new Set();
49
+ }
50
+ }
51
+ /**
52
+ * find all programs used and key-velocity combos in them
53
+ * bank:program each has a set of midiNote-velocity
54
+ * @type {Object<string, Set<string>>}
55
+ */
56
+ const usedProgramsAndKeys = {};
57
+ // check for xg
58
+ let system = "gs";
59
+ mid.tracks.forEach((t, trackNum) => {
60
+ const portOffset = mid.midiPorts[trackNum] * 16;
61
+ for(const event of t)
62
+ {
63
+ const status = event.messageStatusByte & 0xF0;
64
+ if(
65
+ status !== messageTypes.noteOn &&
66
+ status !== messageTypes.controllerChange &&
67
+ status !== messageTypes.programChange &&
68
+ status !== messageTypes.systemExclusive
69
+ )
70
+ {
71
+ continue;
72
+ }
73
+ const channel = (event.messageStatusByte & 0xF) + portOffset;
74
+ let ch = channelPresets[channel];
75
+ switch(status)
76
+ {
77
+ case messageTypes.programChange:
78
+ ch.program = event.messageData[0];
79
+ updateString(ch);
80
+ break;
81
+
82
+ case messageTypes.controllerChange:
83
+ if(event.messageData[0] !== midiControllers.bankSelect)
84
+ {
85
+ continue;
86
+ }
87
+ if(system === "gs" && ch.drums)
88
+ {
89
+ continue;
90
+ }
91
+ const bank = event.messageData[1];
92
+ if(system === "xg")
93
+ {
94
+ const drumsBool = bank === 120 || bank === 126 || bank === 127;
95
+ if(drumsBool !== ch.drums)
96
+ {
97
+ // drum change is a program change
98
+ ch.drums = drumsBool;
99
+ ch.bank = ch.drums ? 128 : bank;
100
+ updateString(ch);
101
+ }
102
+ else
103
+ {
104
+ ch.bank = ch.drums ? 128 : bank;
105
+ }
106
+ continue;
107
+ }
108
+ channelPresets[channel].bank = bank;
109
+ // do not update the data, bank change doesnt change the preset
110
+ break;
111
+
112
+ case messageTypes.noteOn:
113
+ if(event.messageData[1] === 0)
114
+ {
115
+ // that's a note off
116
+ continue;
117
+ }
118
+ if(!usedProgramsAndKeys[ch.string])
119
+ {
120
+ usedProgramsAndKeys[ch.string] = new Set();
121
+ }
122
+ usedProgramsAndKeys[ch.string].add(`${event.messageData[0]}-${event.messageData[1]}`);
123
+ break;
124
+
125
+ case messageTypes.systemExclusive:
126
+ // check for drum sysex
127
+ if(
128
+ event.messageData[0] !== 0x41 || // roland
129
+ event.messageData[2] !== 0x42 || // GS
130
+ event.messageData[3] !== 0x12 || // GS
131
+ event.messageData[4] !== 0x40 || // system parameter
132
+ (event.messageData[5] & 0x10 ) === 0 || // part parameter
133
+ event.messageData[6] !== 0x15 // drum pars
134
+
135
+ )
136
+ {
137
+ // check for XG
138
+ if(
139
+ event.messageData[0] === 0x43 && // yamaha
140
+ event.messageData[2] === 0x4C && // sXG ON
141
+ event.messageData[5] === 0x7E &&
142
+ event.messageData[6] === 0x00
143
+ )
144
+ {
145
+ system = "xg";
146
+ }
147
+ continue;
148
+ }
149
+ const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][event.messageData[5] & 0x0F] + portOffset;
150
+ const isDrum = !!(event.messageData[7] > 0 && event.messageData[5] >> 4);
151
+ ch = channelPresets[sysexChannel];
152
+ ch.drums = isDrum;
153
+ ch.bank = isDrum ? 128 : 0;
154
+ updateString(ch);
155
+ break;
156
+
157
+ }
158
+ }
159
+ });
160
+ for(const key of Object.keys(usedProgramsAndKeys))
161
+ {
162
+ if(usedProgramsAndKeys[key].size === 0)
163
+ {
164
+ SpessaSynthInfo(`%cDetected change but no keys for %c${key}`,
165
+ consoleColors.info,
166
+ consoleColors.value)
167
+ delete usedProgramsAndKeys[key];
168
+ }
169
+ }
170
+ SpessaSynthGroupEnd();
171
+ return usedProgramsAndKeys;
172
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "spessasynth_lib",
3
+ "version": "3.9.12",
4
+ "description": "No compromise SoundFont and MIDI library and player",
5
+ "browser": "index.js",
6
+ "types": "@types/index.d.ts",
7
+ "type": "module",
8
+ "directories": {
9
+ "lib": "lib"
10
+ },
11
+ "scripts": {
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/spessasus/SpessaSynth.git"
17
+ },
18
+ "keywords": [
19
+ "soundfont",
20
+ "synthesizer",
21
+ "synth",
22
+ "sf2",
23
+ "sf3",
24
+ "midi",
25
+ "midi-player",
26
+ "web-audio-api",
27
+ "web-midi-api",
28
+ "player",
29
+ "soundfont2",
30
+ "soundfont3"
31
+ ],
32
+ "author": {
33
+ "name": "spessasus",
34
+ "email": "spesekspesek@gmail.com",
35
+ "url": "https://github.com/spessasus"
36
+ },
37
+ "license": "(MIT AND Apache-2.0)",
38
+ "bugs": {
39
+ "url": "https://github.com/spessasus/SpessaSynth/issues",
40
+ "email": "spesekspesek@gmail.com"
41
+ },
42
+ "homepage": "https://github.com/spessasus/SpessaSynth#readme"
43
+ }
@@ -0,0 +1,23 @@
1
+ ## This is the sequencer's folder.
2
+ The code here is responsible for playing back the parsed MIDI sequence with the synthesizer.
3
+
4
+ ### Message protocol:
5
+ #### Message structure
6
+ ```js
7
+ const message = {
8
+ messageType: number, // WorkletSequencerMessageType
9
+ messageData: any // any
10
+ }
11
+ ```
12
+
13
+ #### To worklet
14
+ Sequencer uses `Synthetizer`'s `post` method to post a message with `messageData` set to `workletMessageType.sequencerSpecific`.
15
+ The `messageData` is set to the sequencer's message.
16
+
17
+ #### From worklet
18
+ `WorkletSequencer` uses `SpessaSynthProcessor`'s post to send a message with `messageData` set to `returnMessageType.sequencerSpecific`.
19
+ The `messageData` is set to the sequencer's return message.
20
+
21
+
22
+ ### Process tick
23
+ `processTick` is called every time the `process` method is called via `SpessaSynthProcessor.processTickCallback`.
@@ -0,0 +1,439 @@
1
+ import { MIDI } from '../midi_parser/midi_loader.js'
2
+ import { Synthetizer } from '../synthetizer/synthetizer.js'
3
+ import { messageTypes } from '../midi_parser/midi_message.js'
4
+ import { workletMessageType } from '../synthetizer/worklet_system/message_protocol/worklet_message.js'
5
+ import {
6
+ WorkletSequencerMessageType,
7
+ WorkletSequencerReturnMessageType,
8
+ } from './worklet_sequencer/sequencer_message.js'
9
+ import { SpessaSynthWarn } from '../utils/loggin.js'
10
+ import { DUMMY_MIDI_DATA, MidiData } from '../midi_parser/midi_data.js'
11
+
12
+ /**
13
+ * sequencer.js
14
+ * purpose: plays back the midi file decoded by midi_loader.js, including support for multi-channel midis (adding channels when more than 1 midi port is detected)
15
+ */
16
+
17
+ /**
18
+ * @typedef MidFile {Object}
19
+ * @property {ArrayBuffer} binary - the binary data of the file.
20
+ * @property {string|undefined} altName - the alternative name for the file
21
+ */
22
+
23
+ /**
24
+ * @typedef {MIDI|MidFile} MIDIFile
25
+ */
26
+
27
+ export class Sequencer
28
+ {
29
+ /**
30
+ * Creates a new Midi sequencer for playing back MIDI files
31
+ * @param midiBinaries {MIDIFile[]} List of the buffers of the MIDI files
32
+ * @param synth {Synthetizer} synth to send events to
33
+ */
34
+ constructor(midiBinaries, synth)
35
+ {
36
+ this.ignoreEvents = false;
37
+ this.synth = synth;
38
+ this.highResTimeOffset = 0;
39
+
40
+ /**
41
+ * Absolute playback startTime, bases on the synth's time
42
+ * @type {number}
43
+ */
44
+ this.absoluteStartTime = this.synth.currentTime;
45
+
46
+ /**
47
+ * @type {function(MIDI)}
48
+ * @private
49
+ */
50
+ this._getMIDIResolve = undefined;
51
+
52
+ /**
53
+ * Controls the playback's rate
54
+ * @type {number}
55
+ */
56
+ this._playbackRate = 1;
57
+
58
+ this.songIndex = 0;
59
+
60
+ this._loop = true;
61
+
62
+ /**
63
+ * Indicates whether the sequencer has finished playing a sequence
64
+ * @type {boolean}
65
+ */
66
+ this.isFinished = false;
67
+
68
+ /**
69
+ * The current sequence's length, in seconds
70
+ * @type {number}
71
+ */
72
+ this.duration = 0;
73
+
74
+ this.synth.sequencerCallbackFunction = this._handleMessage.bind(this);
75
+
76
+ this.loadNewSongList(midiBinaries);
77
+
78
+ window.addEventListener("beforeunload", this.resetMIDIOut.bind(this))
79
+ }
80
+
81
+ resetMIDIOut()
82
+ {
83
+ if(!this.MIDIout)
84
+ {
85
+ return;
86
+ }
87
+ for (let i = 0; i < 16; i++)
88
+ {
89
+ this.MIDIout.send([messageTypes.controllerChange | i, 120, 0]); // all notes off
90
+ this.MIDIout.send([messageTypes.controllerChange | i, 123, 0]); // all sound off
91
+ }
92
+ this.MIDIout.send([messageTypes.reset]); // reset
93
+ }
94
+
95
+ set loop(value)
96
+ {
97
+ this._sendMessage(WorkletSequencerMessageType.setLoop, value);
98
+ this._loop = value;
99
+ }
100
+
101
+ get loop()
102
+ {
103
+ return this._loop;
104
+ }
105
+
106
+ /**
107
+ * @param messageType {WorkletSequencerMessageType}
108
+ * @param messageData {any}
109
+ * @private
110
+ */
111
+ _sendMessage(messageType, messageData = undefined)
112
+ {
113
+ this.synth.post({
114
+ channelNumber: -1,
115
+ messageType: workletMessageType.sequencerSpecific,
116
+ messageData: {
117
+ messageType: messageType,
118
+ messageData: messageData
119
+ }
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Executes when MIDI parsing has an error.
125
+ * @type {function(string)}
126
+ */
127
+ onError;
128
+
129
+ /**
130
+ * @param {WorkletSequencerReturnMessageType} messageType
131
+ * @param {any} messageData
132
+ * @private
133
+ */
134
+ _handleMessage(messageType, messageData)
135
+ {
136
+ if(this.ignoreEvents)
137
+ {
138
+ return;
139
+ }
140
+ switch (messageType)
141
+ {
142
+ default:
143
+ break;
144
+
145
+ case WorkletSequencerReturnMessageType.midiEvent:
146
+ /**
147
+ * @type {number[]}
148
+ */
149
+ let midiEventData = messageData;
150
+ if (this.MIDIout) {
151
+ if (midiEventData[0] >= 0x80) {
152
+ this.MIDIout.send(midiEventData);
153
+ return;
154
+ }
155
+ }
156
+ break;
157
+
158
+ case WorkletSequencerReturnMessageType.songChange:
159
+ /**
160
+ * messageData is expected to be {MidiData}
161
+ * @type {MidiData}
162
+ */
163
+ let songChangeData = messageData[0];
164
+ this.songIndex = messageData[1];
165
+ this.midiData = songChangeData;
166
+ this.absoluteStartTime = 0;
167
+ this.duration = this.midiData.duration;
168
+ Object.entries(this.onSongChange).forEach((callback) => callback[1](songChangeData));
169
+ this.unpause();
170
+ break;
171
+
172
+ case WorkletSequencerReturnMessageType.textEvent:
173
+ /**
174
+ * @type {[Uint8Array, number]}
175
+ */
176
+ let textEventData = messageData;
177
+ if (this.onTextEvent) {
178
+ this.onTextEvent(textEventData[0], textEventData[1]);
179
+ }
180
+ break;
181
+
182
+ case WorkletSequencerReturnMessageType.timeChange:
183
+ // message data is absolute time
184
+ const time = this.synth.currentTime - messageData;
185
+ Object.entries(this.onTimeChange).forEach((callback) => callback[1](time));
186
+ this.unpause();
187
+ this._recalculateStartTime(time);
188
+ break;
189
+
190
+ case WorkletSequencerReturnMessageType.pause:
191
+ this.pausedTime = this.currentTime;
192
+ this.isFinished = messageData;
193
+ break;
194
+
195
+ case WorkletSequencerReturnMessageType.midiError:
196
+ if(this.onError)
197
+ {
198
+ this.onError(messageData);
199
+ }
200
+ else
201
+ {
202
+ throw new Error(messageData);
203
+ }
204
+ return;
205
+
206
+ case WorkletSequencerReturnMessageType.getMIDI:
207
+ if(this._getMIDIResolve)
208
+ {
209
+ this._getMIDIResolve(messageData);
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * @param value {number}
216
+ */
217
+ set playbackRate(value)
218
+ {
219
+ this._sendMessage(WorkletSequencerMessageType.setPlaybackRate, value);
220
+ this.highResTimeOffset *= (value / this._playbackRate);
221
+ this._playbackRate = value;
222
+ }
223
+
224
+ /**
225
+ * @returns {number}
226
+ */
227
+ get playbackRate()
228
+ {
229
+ return this._playbackRate;
230
+ }
231
+
232
+ /**
233
+ * Adds a new event that gets called when the song changes
234
+ * @param callback {function(MidiData)}
235
+ * @param id {string} must be unique
236
+ */
237
+ addOnSongChangeEvent(callback, id)
238
+ {
239
+ this.onSongChange[id] = callback;
240
+ callback(this.midiData);
241
+ }
242
+
243
+ /**
244
+ * Adds a new event that gets called when the time changes
245
+ * @param callback {function(number)} the new time, in seconds
246
+ * @param id {string} must be unique
247
+ */
248
+ addOnTimeChangeEvent(callback, id)
249
+ {
250
+ this.onTimeChange[id] = callback;
251
+ }
252
+
253
+ /**
254
+ * @returns {Promise<MIDI>}
255
+ */
256
+ async getMIDI()
257
+ {
258
+ return new Promise(resolve => {
259
+ this._getMIDIResolve = resolve;
260
+ this._sendMessage(WorkletSequencerMessageType.getMIDI, undefined);
261
+ });
262
+ }
263
+
264
+ /**
265
+ * @param midiBuffers {MIDIFile[]}
266
+ */
267
+ loadNewSongList(midiBuffers)
268
+ {
269
+ this.pause();
270
+ // add some dummy data
271
+ this.midiData = DUMMY_MIDI_DATA;
272
+ this.duration = 99999;
273
+ this._sendMessage(WorkletSequencerMessageType.loadNewSongList, midiBuffers);
274
+ this.songIndex = 0;
275
+ this.songsAmount = midiBuffers.length;
276
+ if(this.songsAmount > 1)
277
+ {
278
+ this.loop = false;
279
+ }
280
+ }
281
+
282
+ nextSong()
283
+ {
284
+ this._sendMessage(WorkletSequencerMessageType.changeSong, true);
285
+ }
286
+
287
+ previousSong()
288
+ {
289
+ this._sendMessage(WorkletSequencerMessageType.changeSong, false);
290
+ }
291
+
292
+ /**
293
+ * @returns {number} Current playback time, in seconds
294
+ */
295
+ get currentTime()
296
+ {
297
+ // return the paused time if it's set to something other than undefined
298
+ if(this.pausedTime)
299
+ {
300
+ return this.pausedTime;
301
+ }
302
+
303
+ return (this.synth.currentTime - this.absoluteStartTime) * this._playbackRate;
304
+ }
305
+
306
+ /**
307
+ * @param time
308
+ * @private
309
+ */
310
+ _recalculateStartTime(time)
311
+ {
312
+ this.absoluteStartTime = this.synth.currentTime - time / this._playbackRate;
313
+ this.highResTimeOffset = (this.synth.currentTime - (performance.now() / 1000)) * this._playbackRate;
314
+ }
315
+
316
+ /**
317
+ * Use for visualization as it's not affected by the audioContext stutter
318
+ * @returns {number}
319
+ */
320
+ get currentHighResolutionTime() {
321
+ if (this.pausedTime) {
322
+ return this.pausedTime;
323
+ }
324
+ const highResTimeOffset = this.highResTimeOffset;
325
+ const absoluteStartTime = this.absoluteStartTime;
326
+
327
+ // sync performance.now to current time
328
+ const performanceElapsedTime = ((performance.now() / 1000) - absoluteStartTime) * this._playbackRate;
329
+
330
+ let currentPerformanceTime = highResTimeOffset + performanceElapsedTime;
331
+ const currentAudioTime = this.currentTime;
332
+
333
+ const smoothingFactor = 0.01 * this._playbackRate;
334
+
335
+ // diff times smoothing factor
336
+ const timeDifference = currentAudioTime - currentPerformanceTime;
337
+ this.highResTimeOffset += timeDifference * smoothingFactor;
338
+
339
+ // return a smoothed performance time
340
+ currentPerformanceTime = this.highResTimeOffset + performanceElapsedTime;
341
+ return currentPerformanceTime;
342
+ }
343
+
344
+
345
+ set currentTime(time)
346
+ {
347
+ this.unpause()
348
+ this._sendMessage(WorkletSequencerMessageType.setTime, time);
349
+ }
350
+
351
+ /**
352
+ * @param output {MIDIOutput}
353
+ */
354
+ connectMidiOutput(output)
355
+ {
356
+ this.resetMIDIOut();
357
+ this.MIDIout = output;
358
+ this._sendMessage(WorkletSequencerMessageType.changeMIDIMessageSending, output !== undefined);
359
+ this.currentTime -= 0.1;
360
+ }
361
+
362
+ /**
363
+ * Pauses the playback
364
+ */
365
+ pause()
366
+ {
367
+ if(this.paused)
368
+ {
369
+ SpessaSynthWarn("Already paused");
370
+ return;
371
+ }
372
+ this.pausedTime = this.currentTime;
373
+ this._sendMessage(WorkletSequencerMessageType.pause);
374
+ }
375
+
376
+ unpause()
377
+ {
378
+ this.pausedTime = undefined;
379
+ this.isFinished = false;
380
+ }
381
+
382
+ /**
383
+ * true if paused, false if playing or stopped
384
+ * @returns {boolean}
385
+ */
386
+ get paused()
387
+ {
388
+ return this.pausedTime !== undefined;
389
+ }
390
+
391
+ /**
392
+ * Starts the playback
393
+ * @param resetTime {boolean} If true, time is set to 0s
394
+ */
395
+ play(resetTime = false)
396
+ {
397
+ if(this.isFinished)
398
+ {
399
+ resetTime = true;
400
+ }
401
+ this._recalculateStartTime(this.pausedTime || 0);
402
+ this.unpause()
403
+ this._sendMessage(WorkletSequencerMessageType.play, resetTime);
404
+ }
405
+
406
+ /**
407
+ * Stops the playback
408
+ */
409
+ stop()
410
+ {
411
+ this._sendMessage(WorkletSequencerMessageType.stop);
412
+ }
413
+
414
+ /**
415
+ * The sequence's data, except for the track data.
416
+ * @type {MidiData}
417
+ */
418
+ midiData;
419
+
420
+ /**
421
+ * @type {Object<string, function(MidiData)>}
422
+ * @private
423
+ */
424
+ onSongChange = {};
425
+
426
+ /**
427
+ * Fires on text event
428
+ * @param data {Uint8Array} the data text
429
+ * @param type {number} the status byte of the message (the meta status byte)
430
+ */
431
+ onTextEvent;
432
+
433
+ /**
434
+ * Fires when CurrentTime changes
435
+ * @type {Object<string, function(number)>} the time that was changed to
436
+ * @private
437
+ */
438
+ onTimeChange = {};
439
+ }