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,193 @@
1
+ import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
2
+ import { consoleColors, formatTime } from '../../utils/other.js'
3
+ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../../utils/loggin.js'
4
+ import { ticksToSeconds } from './play.js'
5
+ import { MidiData } from '../../midi_parser/midi_data.js'
6
+ import { MIDI } from '../../midi_parser/midi_loader.js'
7
+ import { getUsedProgramsAndKeys } from '../../midi_parser/used_keys_loaded.js'
8
+
9
+ /**
10
+ * @param trackNum {number}
11
+ * @param port {number}
12
+ * @this {WorkletSequencer}
13
+ */
14
+ export function assignMIDIPort(trackNum, port)
15
+ {
16
+ // assign new 16 channels if the port is not occupied yet
17
+ if(this.midiPortChannelOffset === 0)
18
+ {
19
+ this.midiPortChannelOffset += 16;
20
+ this.midiPortChannelOffsets[port] = 0;
21
+ }
22
+
23
+ if(this.midiPortChannelOffsets[port] === undefined)
24
+ {
25
+ if(this.synth.workletProcessorChannels.length < this.midiPortChannelOffset + 15)
26
+ {
27
+ this._addNewMidiPort();
28
+ }
29
+ this.midiPortChannelOffsets[port] = this.midiPortChannelOffset;
30
+ this.midiPortChannelOffset += 16;
31
+ }
32
+
33
+ this.midiPorts[trackNum] = port;
34
+ }
35
+
36
+ /**
37
+ * Loads a new sequence
38
+ * @param parsedMidi {MIDI}
39
+ * @this {WorkletSequencer}
40
+ */
41
+ export function loadNewSequence(parsedMidi)
42
+ {
43
+ this.stop();
44
+ if (!parsedMidi.tracks)
45
+ {
46
+ throw "No tracks supplied!";
47
+ }
48
+
49
+ this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision)
50
+
51
+ /**
52
+ * @type {MIDI}
53
+ */
54
+ this.midiData = parsedMidi;
55
+
56
+ // check for embedded soundfont
57
+ if(this.midiData.embeddedSoundFont !== undefined)
58
+ {
59
+ this.synth.reloadSoundFont(this.midiData.embeddedSoundFont);
60
+ // preload all samples
61
+ this.synth.soundfont.samples.forEach(s => s.getAudioData());
62
+ }
63
+ else
64
+ {
65
+ SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
66
+ // smart preloading: load only samples used in the midi!
67
+ const used = getUsedProgramsAndKeys(this.midiData, this.synth.soundfont);
68
+ for (const [programBank, combos] of Object.entries(used))
69
+ {
70
+ const bank = parseInt(programBank.split(":")[0]);
71
+ const program = parseInt(programBank.split(":")[1]);
72
+ const preset = this.synth.soundfont.getPreset(bank, program);
73
+ SpessaSynthInfo(`%cPreloading used samples on %c${preset.presetName}%c...`,
74
+ consoleColors.info,
75
+ consoleColors.recognized,
76
+ consoleColors.info)
77
+ for (const combo of combos) {
78
+ const split = combo.split("-");
79
+ preset.preloadSpecific(parseInt(split[0]), parseInt(split[1]));
80
+ }
81
+ }
82
+ SpessaSynthGroupEnd();
83
+ }
84
+
85
+ /**
86
+ * the midi track data
87
+ * @type {MidiMessage[][]}
88
+ */
89
+ this.tracks = this.midiData.tracks;
90
+
91
+ // clear last port data
92
+ this.midiPortChannelOffset = 0;
93
+ this.midiPortChannelOffsets = {};
94
+ // copy over the port data
95
+ this.midiPorts = this.midiData.midiPorts;
96
+
97
+ // assign port offsets
98
+ this.midiData.midiPorts.forEach((port, trackIndex) => {
99
+ this.assignMIDIPort(trackIndex, port);
100
+ });
101
+
102
+ /**
103
+ * Same as Audio.duration (seconds)
104
+ * @type {number}
105
+ */
106
+ this.duration = this.midiData.duration;
107
+ this.firstNoteTime = ticksToSeconds(this.midiData.tempoChanges, this.midiData.firstNoteOn, this.midiData.timeDivision);
108
+ SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
109
+
110
+ this.post(WorkletSequencerReturnMessageType.songChange, [new MidiData(this.midiData), this.songIndex]);
111
+
112
+ this.synth.resetAllControllers();
113
+ if(this.duration <= 1)
114
+ {
115
+ SpessaSynthWarn(`%cVery short song: (${formatTime(Math.round(this.duration)).time}). Disabling loop!`,
116
+ consoleColors.warn);
117
+ this.loop = false;
118
+ }
119
+ this.play(true);
120
+ }
121
+
122
+ /**
123
+ * @param midiBuffers {MIDIFile[]}
124
+ * @this {WorkletSequencer}
125
+ */
126
+ export function loadNewSongList(midiBuffers)
127
+ {
128
+ /**
129
+ * parse the MIDIs (only the array buffers, MIDI is unchanged)
130
+ * @type {MIDI[]}
131
+ */
132
+ this.songs = midiBuffers.reduce((mids, b) => {
133
+ if(b.duration)
134
+ {
135
+ mids.push(b);
136
+ return mids;
137
+ }
138
+ try
139
+ {
140
+ mids.push(new MIDI(b.binary, b.altName || ""));
141
+ }
142
+ catch (e)
143
+ {
144
+ this.post(WorkletSequencerReturnMessageType.midiError, e.message);
145
+ return mids;
146
+ }
147
+ return mids;
148
+ }, []);
149
+ if(this.songs.length < 1)
150
+ {
151
+ console.log("no valid songs!")
152
+ return;
153
+ }
154
+ this.songIndex = 0;
155
+ if(this.songs.length > 1)
156
+ {
157
+ this.loop = false;
158
+ }
159
+ this.loadNewSequence(this.songs[this.songIndex]);
160
+ }
161
+
162
+ /**
163
+ * @this {WorkletSequencer}
164
+ */
165
+ export function nextSong()
166
+ {
167
+ if(this.songs.length === 1)
168
+ {
169
+ this.currentTime = 0;
170
+ return;
171
+ }
172
+ this.songIndex++;
173
+ this.songIndex %= this.songs.length;
174
+ this.loadNewSequence(this.songs[this.songIndex]);
175
+ }
176
+
177
+ /**
178
+ * @this {WorkletSequencer}
179
+ */
180
+ export function previousSong()
181
+ {
182
+ if(this.songs.length === 1)
183
+ {
184
+ this.currentTime = 0;
185
+ return;
186
+ }
187
+ this.songIndex--;
188
+ if(this.songIndex < 0)
189
+ {
190
+ this.songIndex = this.songs.length - 1;
191
+ }
192
+ this.loadNewSequence(this.songs[this.songIndex]);
193
+ }
@@ -0,0 +1,218 @@
1
+ import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
2
+ import { _addNewMidiPort, _processEvent } from './process_event.js'
3
+ import { _findFirstEventIndex, _processTick } from './process_tick.js'
4
+ import { assignMIDIPort, loadNewSequence, loadNewSongList, nextSong, previousSong } from './song_control.js'
5
+ import { _playTo, _recalculateStartTime, play, setTimeTicks } from './play.js'
6
+ import { messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
7
+ import { post, processMessage, sendMIDIMessage } from './events.js'
8
+ import { SpessaSynthWarn } from '../../utils/loggin.js'
9
+ import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
10
+
11
+ class WorkletSequencer
12
+ {
13
+ /**
14
+ * @param spessasynthProcessor {SpessaSynthProcessor}
15
+ */
16
+ constructor(spessasynthProcessor)
17
+ {
18
+ this.synth = spessasynthProcessor;
19
+ this.ignoreEvents = false;
20
+
21
+ /**
22
+ * If the event should instead be sent back to the main thread instead of synth
23
+ * @type {boolean}
24
+ */
25
+ this.sendMIDIMessages = false;
26
+
27
+ // event's number in this.events
28
+ /**
29
+ * @type {number[]}
30
+ */
31
+ this.eventIndex = [];
32
+ this.songIndex = 0;
33
+
34
+ // tracks the time that we have already played
35
+ /**
36
+ * @type {number}
37
+ */
38
+ this.playedTime = 0;
39
+
40
+ /**
41
+ * The (relative) time when the sequencer was paused. If it's not paused then it's undefined.
42
+ * @type {number}
43
+ */
44
+ this.pausedTime = undefined;
45
+
46
+ /**
47
+ * Absolute playback startTime, bases on the synth's time
48
+ * @type {number}
49
+ */
50
+ this.absoluteStartTime = currentTime;
51
+
52
+ /**
53
+ * Controls the playback's rate
54
+ * @type {number}
55
+ */
56
+ this._playbackRate = 1;
57
+
58
+ /**
59
+ * Currently playing notes (for pausing and resuming)
60
+ * @type {{
61
+ * midiNote: number,
62
+ * channel: number,
63
+ * velocity: number,
64
+ * startTime: number
65
+ * }[]}
66
+ */
67
+ this.playingNotes = [];
68
+
69
+ // controls if the sequencer loops (defaults to true)
70
+ this.loop = true;
71
+
72
+ /**
73
+ * the current track data
74
+ * @type {MIDI}
75
+ */
76
+ this.midiData = undefined;
77
+
78
+ /**
79
+ * midi port number for the corresponding track
80
+ * @type {number[]}
81
+ */
82
+ this.midiPorts = [];
83
+
84
+ this.midiPortChannelOffset = 0;
85
+
86
+ /**
87
+ * midi port: channel offset
88
+ * @type {Object<number, number>}
89
+ */
90
+ this.midiPortChannelOffsets = {};
91
+ }
92
+
93
+ /**
94
+ * @param value {number}
95
+ */
96
+ set playbackRate(value)
97
+ {
98
+ const time = this.currentTime;
99
+ this._playbackRate = value;
100
+ this.currentTime = time;
101
+ }
102
+
103
+ get currentTime()
104
+ {
105
+ // return the paused time if it's set to something other than undefined
106
+ if(this.pausedTime)
107
+ {
108
+ return this.pausedTime;
109
+ }
110
+
111
+ return (currentTime - this.absoluteStartTime) * this._playbackRate;
112
+ }
113
+
114
+ set currentTime(time)
115
+ {
116
+ if(time < this.firstNoteTime || time > this.duration)
117
+ {
118
+ // time is 0
119
+ this.setTimeTicks(this.midiData.firstNoteOn - 1);
120
+ return;
121
+ }
122
+ this.stop();
123
+ this.playingNotes = [];
124
+ this.pausedTime = undefined;
125
+ this.post(WorkletSequencerReturnMessageType.timeChange, currentTime - time);
126
+ const isNotFinished = this._playTo(time);
127
+ this._recalculateStartTime(time);
128
+ if(!isNotFinished)
129
+ {
130
+ return;
131
+ }
132
+ this.play();
133
+ }
134
+
135
+ /**
136
+ * Pauses the playback
137
+ * @param isFinished {boolean}
138
+ */
139
+ pause(isFinished = false)
140
+ {
141
+ if(this.paused)
142
+ {
143
+ SpessaSynthWarn("Already paused");
144
+ return;
145
+ }
146
+ this.pausedTime = this.currentTime;
147
+ this.stop();
148
+ this.post(WorkletSequencerReturnMessageType.pause, isFinished);
149
+ }
150
+
151
+ /**
152
+ * Stops the playback
153
+ */
154
+ stop()
155
+ {
156
+ this.clearProcessHandler()
157
+ // disable sustain
158
+ for (let i = 0; i < 16; i++) {
159
+ this.synth.controllerChange(i, midiControllers.sustainPedal, 0);
160
+ }
161
+ this.synth.stopAllChannels();
162
+ if(this.sendMIDIMessages)
163
+ {
164
+ for (let c = 0; c < MIDI_CHANNEL_COUNT; c++)
165
+ {
166
+ this.sendMIDIMessage([messageTypes.controllerChange | c, 120, 0]); // all notes off
167
+ this.sendMIDIMessage([messageTypes.controllerChange | c, 123, 0]); // all sound off
168
+ }
169
+ }
170
+ }
171
+
172
+ _resetTimers()
173
+ {
174
+ this.playedTime = 0
175
+ this.eventIndex = Array(this.tracks.length).fill(0);
176
+ }
177
+
178
+ /**
179
+ * true if paused, false if playing or stopped
180
+ * @returns {boolean}
181
+ */
182
+ get paused()
183
+ {
184
+ return this.pausedTime !== undefined;
185
+ }
186
+
187
+ setProcessHandler()
188
+ {
189
+ this.synth.processTickCallback = this._processTick.bind(this);
190
+ }
191
+
192
+ clearProcessHandler()
193
+ {
194
+ this.synth.processTickCallback = undefined;
195
+ }
196
+ }
197
+
198
+ WorkletSequencer.prototype.post = post;
199
+ WorkletSequencer.prototype.sendMIDIMessage = sendMIDIMessage;
200
+ WorkletSequencer.prototype.assignMIDIPort = assignMIDIPort;
201
+ WorkletSequencer.prototype.processMessage = processMessage;
202
+
203
+ WorkletSequencer.prototype._processEvent = _processEvent;
204
+ WorkletSequencer.prototype._addNewMidiPort = _addNewMidiPort;
205
+ WorkletSequencer.prototype._processTick = _processTick;
206
+ WorkletSequencer.prototype._findFirstEventIndex = _findFirstEventIndex;
207
+
208
+ WorkletSequencer.prototype.loadNewSequence = loadNewSequence;
209
+ WorkletSequencer.prototype.loadNewSongList = loadNewSongList;
210
+ WorkletSequencer.prototype.nextSong = nextSong;
211
+ WorkletSequencer.prototype.previousSong = previousSong;
212
+
213
+ WorkletSequencer.prototype.play = play;
214
+ WorkletSequencer.prototype._playTo = _playTo;
215
+ WorkletSequencer.prototype.setTimeTicks = setTimeTicks;
216
+ WorkletSequencer.prototype._recalculateStartTime = _recalculateStartTime;
217
+
218
+ export { WorkletSequencer }
@@ -0,0 +1,8 @@
1
+ ## This is the SoundFont2 parsing library.
2
+ The code here is responsible for parsing the SoundFont2 file and
3
+ providing an easy way to get the data out.
4
+ Default modulators are also stored here (in `modulators.js`)
5
+
6
+ `read` folder contains the classes that represent the soundfont file
7
+
8
+ `write` folder contains the code for writing out an `.sf2` file.
@@ -0,0 +1,212 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { RiffChunk } from './riff_chunk.js'
3
+ import { signedInt16 } from '../../utils/byte_functions/little_endian.js'
4
+
5
+ /**
6
+ * generators.js
7
+ * purpose: contains enums for generators and their limis parses reads soundfont generators, sums them and applies limits
8
+ */
9
+
10
+ /**
11
+ * @enum {number}
12
+ */
13
+ export const generatorTypes = {
14
+ startAddrsOffset: 0, // sample control - moves sample start point
15
+ endAddrOffset: 1, // sample control - moves sample end point
16
+ startloopAddrsOffset: 2, // loop control - moves loop start point
17
+ endloopAddrsOffset: 3, // loop control - moves loop end point
18
+ startAddrsCoarseOffset: 4, // sample control - moves sample start point in 32767 increments
19
+ modLfoToPitch: 5, // pitch modulation - modulation lfo pitch modulation in cents
20
+ vibLfoToPitch: 6, // pitch modulation - vibrato lfo pitch modulation in cents
21
+ modEnvToPitch: 7, // pitch modulation - modulation envelope pitch modulation in cents
22
+ initialFilterFc: 8, // filter - lowpass filter cutoff in cents
23
+ initialFilterQ: 9, // filter - lowpass filter resonance
24
+ modLfoToFilterFc: 10, // filter modulation - modulation lfo lowpass filter cutoff in cents
25
+ modEnvToFilterFc: 11, // filter modulation - modulation envelope lowpass filter cutoff in cents
26
+ endAddrsCoarseOffset: 12, // ample control - moves sample end point in 32767 increments
27
+ modLfoToVolume: 13, // modulation lfo - volume (tremolo), where 100 = 10dB
28
+ unused1: 14,
29
+ chorusEffectsSend: 15, // effect send - how much is sent to chorus 0 - 1000
30
+ reverbEffectsSend: 16, // effect send - how much is sent to reverb 0 - 1000
31
+ pan: 17, // panning - where -500 = left, 0 = center, 500 = right
32
+ unused2: 18,
33
+ unused3: 19,
34
+ unused4: 20,
35
+ delayModLFO: 21, // mod lfo - delay for mod lfo to start from zero (weird scale)
36
+ freqModLFO: 22, // mod lfo - frequency of mod lfo, 0 = 8.176Hz, unit: f => 1200log2(f/8.176)
37
+ delayVibLFO: 23, // vib lfo - delay for vibrato lfo to start from zero (weird scale)
38
+ freqVibLFO: 24, // vib lfo - frequency of vibrato lfo, 0 = 8.176Hz, unit: f => 1200log2(f/8.176)
39
+ delayModEnv: 25, // mod env - 0 = 1s declay till mod env starts
40
+ attackModEnv: 26, // mod env - attack of mod env
41
+ holdModEnv: 27, // mod env - hold of mod env
42
+ decayModEnv: 28, // mod env - decay of mod env
43
+ sustainModEnv: 29, // mod env - sustain of mod env
44
+ releaseModEnv: 30, // mod env - release of mod env
45
+ keyNumToModEnvHold: 31, // mod env - also modulating mod envelope hold with key number
46
+ keyNumToModEnvDecay: 32, // mod env - also modulating mod envelope decay with key number
47
+ delayVolEnv: 33, // vol env - delay of envelope from zero (weird scale)
48
+ attackVolEnv: 34, // vol env - attack of envelope
49
+ holdVolEnv: 35, // vol env - hold of envelope
50
+ decayVolEnv: 36, // vol env - decay of envelope
51
+ sustainVolEnv: 37, // vol env - sustain of envelope
52
+ releaseVolEnv: 38, // vol env - release of envelope
53
+ keyNumToVolEnvHold: 39, // vol env - key number to volume envelope hold
54
+ keyNumToVolEnvDecay: 40, // vol env - key number to volume envelope decay
55
+ instrument: 41, // zone - instrument index to use for preset zone
56
+ reserved1: 42,
57
+ keyRange: 43, // zone - key range for which preset / instrument zone is active
58
+ velRange: 44, // zone - velocity range for which preset / instrument zone is active
59
+ startloopAddrsCoarseOffset: 45, // ample control - moves sample loop start point in 32767 increments
60
+ keyNum: 46, // zone - instrument only: always use this midi number (ignore what's pressed)
61
+ velocity: 47, // zone - instrument only: always use this velocity (ignore what's pressed)
62
+ initialAttenuation: 48, // zone - allows turning down the volume, 10 = -1dB
63
+ reserved2: 49,
64
+ endloopAddrsCoarseOffset: 50, // ample control - moves sample loop end point in 32767 increments
65
+ coarseTune: 51, // tune - pitch offset in semitones
66
+ fineTune: 52, // tune - pitch offset in cents
67
+ sampleID: 53, // sample - instrument zone only: which sample to use
68
+ sampleModes: 54, // sample - 0 = no loop, 1 = loop, 2 = reserved, 3 = loop and play till end in release phase
69
+ reserved3: 55,
70
+ scaleTuning: 56, // sample - the degree to which MIDI key number influences pitch, 100 = default
71
+ exclusiveClass: 57, // sample - = cut = choke group
72
+ overridingRootKey: 58, // sample - can override the sample's original pitch
73
+ unused5: 59,
74
+ endOper: 60 // end marker
75
+ };
76
+
77
+ /**
78
+ * @type {{min: number, max: number, def: number}[]}
79
+ */
80
+ export const generatorLimits = [];
81
+ // offsets
82
+ generatorLimits[generatorTypes.startAddrsOffset] = {min: 0, max: 32768, def: 0};
83
+ generatorLimits[generatorTypes.endAddrOffset] = {min: -32768, max: 32768, def: 0};
84
+ generatorLimits[generatorTypes.startloopAddrsOffset] = {min: -32768, max: 32768, def: 0};
85
+ generatorLimits[generatorTypes.endloopAddrsOffset] = {min: -32768, max: 32768, def: 0};
86
+ generatorLimits[generatorTypes.startAddrsCoarseOffset] = {min: 0, max: 32768, def: 0};
87
+
88
+ // pitch influence
89
+ generatorLimits[generatorTypes.modLfoToPitch] = {min: -12000, max: 12000, def: 0};
90
+ generatorLimits[generatorTypes.vibLfoToPitch] = {min: -12000, max: 12000, def: 0};
91
+ generatorLimits[generatorTypes.modEnvToPitch] = {min: -12000, max: 12000, def: 0};
92
+
93
+ // lowpass
94
+ generatorLimits[generatorTypes.initialFilterFc] = {min: 1500, max: 13500, def: 13500};
95
+ generatorLimits[generatorTypes.initialFilterQ] = {min: 0, max: 960, def: 0};
96
+ generatorLimits[generatorTypes.modLfoToFilterFc] = {min: -12000, max: 12000, def: 0};
97
+ generatorLimits[generatorTypes.modEnvToFilterFc] = {min: -12000, max: 12000, def: 0};
98
+
99
+ generatorLimits[generatorTypes.endAddrsCoarseOffset] = {min: -32768, max: 32768, def: 0};
100
+
101
+ generatorLimits[generatorTypes.modLfoToVolume] = {min: -960, max: 960, def: 0};
102
+
103
+ // effects, pan
104
+ generatorLimits[generatorTypes.chorusEffectsSend] = {min: 0, max: 1000, def: 0};
105
+ generatorLimits[generatorTypes.reverbEffectsSend] = {min: 0, max: 1000, def: 0};
106
+ generatorLimits[generatorTypes.pan] = {min: -500, max: 500, def: 0};
107
+
108
+ // lfo
109
+ generatorLimits[generatorTypes.delayModLFO] = {min: -12000, max: 5000, def: -12000};
110
+ generatorLimits[generatorTypes.freqModLFO] = {min: -16000, max: 4500, def: 0};
111
+ generatorLimits[generatorTypes.delayVibLFO] = {min: -12000, max: 5000, def: -12000};
112
+ generatorLimits[generatorTypes.freqVibLFO] = {min: -16000, max: 4500, def: 0};
113
+
114
+ // mod env
115
+ generatorLimits[generatorTypes.delayModEnv] = {min: -12000, max: 5000, def: -12000};
116
+ generatorLimits[generatorTypes.attackModEnv] = {min: -12000, max: 8000, def: -12000};
117
+ generatorLimits[generatorTypes.holdModEnv] = {min: -12000, max: 5000, def: -12000};
118
+ generatorLimits[generatorTypes.decayModEnv] = {min: -12000, max: 8000, def: -12000};
119
+ generatorLimits[generatorTypes.sustainModEnv] = {min: 0, max: 1000, def: 0};
120
+ generatorLimits[generatorTypes.releaseModEnv] = {min: -12000, max: 8000, def: -12000};
121
+ // keynum to mod env
122
+ generatorLimits[generatorTypes.keyNumToModEnvHold] = {min: -1200, max: 1200, def: 0};
123
+ generatorLimits[generatorTypes.keyNumToModEnvDecay] = {min: -1200, max: 1200, def: 0};
124
+
125
+ // vol env
126
+ generatorLimits[generatorTypes.delayVolEnv] = {min: -12000, max: 5000, def: -12000};
127
+ generatorLimits[generatorTypes.attackVolEnv] = {min: -12000, max: 8000, def: -12000};
128
+ generatorLimits[generatorTypes.holdVolEnv] = {min: -12000, max: 5000, def: -12000};
129
+ generatorLimits[generatorTypes.decayVolEnv] = {min: -12000, max: 8000, def: -12000};
130
+ generatorLimits[generatorTypes.sustainVolEnv] = {min: 0, max: 1440, def: 0};
131
+ generatorLimits[generatorTypes.releaseVolEnv] = {min: -7200, max: 8000, def: -12000}; // prevent clicks
132
+ // keynum to vol env
133
+ generatorLimits[generatorTypes.keyNumToVolEnvHold] = {min: -1200, max: 1200, def: 0};
134
+ generatorLimits[generatorTypes.keyNumToVolEnvDecay] = {min: -1200, max: 1200, def: 0};
135
+
136
+ generatorLimits[generatorTypes.startloopAddrsCoarseOffset] = {min: -32768, max: 32768, def: 0};
137
+ generatorLimits[generatorTypes.keyNum] = {min: -1, max: 127, def: -1};
138
+ generatorLimits[generatorTypes.velocity] = {min: -1, max: 127, def: -1};
139
+
140
+ generatorLimits[generatorTypes.initialAttenuation] = {min: -250, max: 1440, def: 0}; // soundblaster allows 10dB of gain (divide by 0.4)
141
+
142
+ generatorLimits[generatorTypes.endloopAddrsCoarseOffset] = {min: -32768, max: 32768, def: 0};
143
+
144
+ generatorLimits[generatorTypes.coarseTune] = {min: -120, max: 120, def: 0};
145
+ generatorLimits[generatorTypes.fineTune] = {min: -99, max: 99, def: 0};
146
+ generatorLimits[generatorTypes.scaleTuning] = {min: 0, max: 1200, def: 100};
147
+ generatorLimits[generatorTypes.exclusiveClass] = {min: 0, max: 99999, def: 0};
148
+ generatorLimits[generatorTypes.overridingRootKey] = {min: 0-1, max: 127, def: -1};
149
+
150
+
151
+ /**
152
+ * @param generatorType {number}
153
+ * @param presetGens {Generator[]}
154
+ * @param instrumentGens {Generator[]}
155
+ */
156
+ export function addAndClampGenerator(generatorType, presetGens, instrumentGens)
157
+ {
158
+ const limits = generatorLimits[generatorType] || {min: 0, max: 32768, def: 0};
159
+ let presetGen = presetGens.find(g => g.generatorType === generatorType);
160
+ let presetValue = 0;
161
+ if(presetGen)
162
+ {
163
+ presetValue = presetGen.generatorValue;
164
+ }
165
+
166
+ let instruGen = instrumentGens.find(g => g.generatorType === generatorType);
167
+ let instruValue = limits.def;
168
+ if(instruGen)
169
+ {
170
+ instruValue = instruGen.generatorValue;
171
+ }
172
+ return Math.max(limits.min, Math.min(limits.max, instruValue + presetValue));
173
+ }
174
+
175
+
176
+ export class Generator{
177
+ /**
178
+ * Creates a generator
179
+ * @param dataArray {IndexedByteArray}
180
+ */
181
+ constructor(dataArray) {
182
+ // 4 bytes:
183
+ // type, type, type, value
184
+ const i = dataArray.currentIndex;
185
+ /**
186
+ * @type {generatorTypes}
187
+ **/
188
+ this.generatorType = (dataArray[i + 1] << 8) | dataArray[i];
189
+ this.generatorValue = signedInt16(dataArray[i + 2], dataArray[i + 3]);
190
+ dataArray.currentIndex += 4;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Reads the generator read
196
+ * @param generatorChunk {RiffChunk}
197
+ * @returns {Generator[]}
198
+ */
199
+ export function readGenerators(generatorChunk)
200
+ {
201
+ let gens = [];
202
+ while(generatorChunk.chunkData.length > generatorChunk.chunkData.currentIndex)
203
+ {
204
+ gens.push(new Generator(generatorChunk.chunkData));
205
+ }
206
+ if(gens.length > 1)
207
+ {
208
+ // remove terminal
209
+ gens.pop();
210
+ }
211
+ return gens;
212
+ }