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,116 @@
1
+ import { midiControllers } from '../../../midi_parser/midi_message.js'
2
+ import { modulatorSources } from '../../../soundfont/read/modulators.js'
3
+ /**
4
+ * @typedef {Object} WorkletProcessorChannel
5
+ * @property {Int16Array} midiControllers - array of MIDI controller values + the values used by modulators as source (pitch bend, bend range etc.)
6
+ * @property {boolean[]} lockedControllers - array indicating if a controller is locked
7
+ * @property {Float32Array} customControllers - array of custom (not sf2) control values such as RPN pitch tuning, transpose, modulation depth, etc.
8
+ *
9
+ * @property {number} channelTransposeKeyShift - key shift of the channel
10
+ * @property {boolean} holdPedal - indicates whether the hold pedal is active
11
+ * @property {boolean} drumChannel - indicates whether the channel is a drum channel
12
+ *
13
+ * @property {dataEntryStates} dataEntryState - the current state of the data entry
14
+ * @property {number} NRPCoarse - the current coarse value of the Non-Registered Parameter
15
+ * @property {number} NRPFine - the current fine value of the Non-Registered Parameter
16
+ * @property {number} RPValue - the current value of the Registered Parameter
17
+ *
18
+ * @property {Preset} preset - the channel's preset
19
+ * @property {boolean} lockPreset - indicates whether the program on the channel is locked
20
+ *
21
+ * @property {boolean} lockVibrato - indicates whether the custom vibrato is locked
22
+ * @property {Object} channelVibrato - vibrato settings for the channel
23
+ * @property {number} channelVibrato.depth - depth of the vibrato effect (cents)
24
+ * @property {number} channelVibrato.delay - delay before the vibrato effect starts (seconds)
25
+ * @property {number} channelVibrato.rate - rate of the vibrato oscillation (Hz)
26
+
27
+ * @property {boolean} isMuted - indicates whether the channel is muted
28
+ * @property {WorkletVoice[]} voices - array of voices currently active on the channel
29
+ * @property {WorkletVoice[]} sustainedVoices - array of voices that are sustained on the channel
30
+ * @property {WorkletVoice[][][]} cachedVoices - first is midi note, second is velocity. output is an array of WorkletVoices
31
+ */
32
+
33
+ /**
34
+ * @param sendEvent {boolean}
35
+ * @this {SpessaSynthProcessor}
36
+ */
37
+ export function createWorkletChannel(sendEvent = false)
38
+ {
39
+ /**
40
+ * @type {WorkletProcessorChannel}
41
+ */
42
+ const channel = {
43
+ midiControllers: new Int16Array(CONTROLLER_TABLE_SIZE),
44
+ lockedControllers: Array(CONTROLLER_TABLE_SIZE).fill(false),
45
+ customControllers: new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE),
46
+
47
+ NRPCoarse: 0,
48
+ NRPFine: 0,
49
+ RPValue: 0,
50
+ dataEntryState: dataEntryStates.Idle,
51
+
52
+ voices: [],
53
+ sustainedVoices: [],
54
+ cachedVoices: [],
55
+ preset: this.defaultPreset,
56
+
57
+ channelTransposeKeyShift: 0,
58
+ channelVibrato: {delay: 0, depth: 0, rate: 0},
59
+ lockVibrato: false,
60
+ holdPedal: false,
61
+ isMuted: false,
62
+ drumChannel: false,
63
+ lockPreset: false,
64
+
65
+ }
66
+ for (let i = 0; i < 128; i++)
67
+ {
68
+ channel.cachedVoices.push([]);
69
+ }
70
+ this.workletProcessorChannels.push(channel);
71
+ this.resetControllers(this.workletProcessorChannels.length - 1);
72
+ this.sendChannelProperties();
73
+ if(sendEvent)
74
+ {
75
+ this.callEvent("newchannel", undefined);
76
+ }
77
+ }
78
+
79
+ export const NON_CC_INDEX_OFFSET = 128;
80
+ export const CONTROLLER_TABLE_SIZE = 147;
81
+ // an array with preset default values so we can quickly use set() to reset the controllers
82
+ export const resetArray = new Int16Array(CONTROLLER_TABLE_SIZE).fill(0);
83
+ // default values (the array is 14 bit so shift the 7 bit values by 7 bits)
84
+ resetArray[midiControllers.mainVolume] = 100 << 7;
85
+ resetArray[midiControllers.expressionController] = 127 << 7;
86
+ resetArray[midiControllers.pan] = 64 << 7;
87
+ resetArray[midiControllers.releaseTime] = 64 << 7;
88
+ resetArray[midiControllers.brightness] = 64 << 7;
89
+ resetArray[midiControllers.effects1Depth] = 40 << 7;
90
+ resetArray[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = 8192;
91
+ resetArray[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] = 2 << 7;
92
+
93
+ /**
94
+ * @enum {number}
95
+ */
96
+ export const dataEntryStates = {
97
+ Idle: 0,
98
+ RPCoarse: 1,
99
+ RPFine: 2,
100
+ NRPCoarse: 3,
101
+ NRPFine: 4,
102
+ DataCoarse: 5,
103
+ DataFine: 6
104
+ };
105
+
106
+
107
+ export const customControllers = {
108
+ channelTuning: 0, // cents, RPN for fine tuning
109
+ channelTransposeFine: 1, // cents, only the decimal tuning, (e.g. transpose is 4.5, then shift by 4 keys + tune by 50 cents)
110
+ modulationMultiplier: 2, // cents, set by moduldation depth RPN
111
+ masterTuning: 3, // cents, set by system exclusive
112
+ channelTuningSemitones: 4, // semitones, for RPN coarse tuning
113
+ }
114
+ export const CUSTOM_CONTROLLER_TABLE_SIZE = Object.keys(customControllers).length;
115
+ export const customResetArray = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
116
+ customResetArray[customControllers.modulationMultiplier] = 1;
@@ -0,0 +1,272 @@
1
+ /**
2
+ * worklet_voice.js
3
+ * purpose: prepares workletvoices from sample and generator data and manages sample dumping
4
+ * note: sample dumping means sending it over to the AudioWorkletGlobalScope
5
+ */
6
+
7
+ /**
8
+ * @typedef {Object} WorkletSample
9
+ * @property {number} sampleID - ID of the sample
10
+ * @property {number} playbackStep - current playback step (rate)
11
+ * @property {number} cursor - current position in the sample
12
+ * @property {number} rootKey - root key of the sample
13
+ * @property {number} loopStart - start position of the loop
14
+ * @property {number} loopEnd - end position of the loop
15
+ * @property {number} end - end position of the sample
16
+ * @property {0|1|2} loopingMode - looping mode of the sample
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} WorkletVoice
21
+ * @property {WorkletSample} sample - sample ID for voice.
22
+ * @property {WorkletLowpassFilter} filter - lowpass filter applied to the voice
23
+ * @property {Int16Array} generators - the unmodulated (constant) generators of the voice
24
+ * @property {Modulator[]} modulators - the voice's modulators. Grouped by the destination
25
+ * @property {Int16Array} modulatedGenerators - the generators modulated by the modulators
26
+ *
27
+ * @property {boolean} finished - indicates if the voice has finished
28
+ * @property {boolean} isInRelease - indicates if the voice is in the release phase
29
+ *
30
+ * @property {number} channelNumber - MIDI channel number
31
+ * @property {number} velocity - velocity of the note
32
+ * @property {number} midiNote - MIDI note number
33
+ * @property {number} pressure - the pressure of the note
34
+ * @property {number} targetKey - target key for the note
35
+ *
36
+ * @property {WorkletVolumeEnvelope} volumeEnvelope
37
+ *
38
+ * @property {number} currentModEnvValue - current value of the modulation envelope
39
+ * @property {number} releaseStartModEnv - modenv value at the start of the release phase
40
+ *
41
+ * @property {number} startTime - start time of the voice
42
+ * @property {number} releaseStartTime - start time of the release phase
43
+ *
44
+ * @property {number} currentTuningCents - current tuning adjustment in cents
45
+ * @property {number} currentTuningCalculated - calculated tuning adjustment
46
+ * @property {number} currentPan - from 0 to 1
47
+ */
48
+
49
+ import { addAndClampGenerator, generatorTypes } from '../../../soundfont/read/generators.js'
50
+ import { SpessaSynthTable, SpessaSynthWarn } from '../../../utils/loggin.js'
51
+ import { DEFAULT_WORKLET_VOLUME_ENVELOPE } from './volume_envelope.js'
52
+ import { DEFAULT_WORKLET_LOWPASS_FILTER } from './lowpass_filter.js'
53
+
54
+
55
+ /**
56
+ * the sampleID is the index
57
+ * @type {boolean[]}
58
+ */
59
+ let globalDumpedSamplesList = [];
60
+
61
+ export function clearSamplesList()
62
+ {
63
+ globalDumpedSamplesList = [];
64
+ }
65
+
66
+ function /**
67
+ * @param channel {number} channel hint for the processor to recalculate cursor positions
68
+ * @param sample {Sample}
69
+ * @param id {number}
70
+ * @param sampleDumpCallback {function({channel: number, sampleID: number, sampleData: Float32Array})}
71
+ */
72
+ dumpSample(channel, sample, id, sampleDumpCallback)
73
+ {
74
+ // flag as defined, so it's currently being dumped
75
+ globalDumpedSamplesList[id] = false;
76
+
77
+ // load the data
78
+ sampleDumpCallback({
79
+ channel: channel,
80
+ sampleID: id,
81
+ sampleData: sample.getAudioData()
82
+ });
83
+ globalDumpedSamplesList[id] = true;
84
+ }
85
+
86
+ /**
87
+ * Deep clone function for the WorkletVoice object and its nested structures.
88
+ * This function handles Int16Array, objects, arrays, and primitives.
89
+ * It does not handle circular references.
90
+ * @param {WorkletVoice} obj - The object to clone.
91
+ * @returns {WorkletVoice} - Cloned object.
92
+ */
93
+ function deepClone(obj) {
94
+ if (obj === null || typeof obj !== 'object') {
95
+ return obj;
96
+ }
97
+
98
+ // Handle Int16Array separately
99
+ if (obj instanceof Int16Array) {
100
+ return new Int16Array(obj);
101
+ }
102
+
103
+ // Handle objects and arrays
104
+ const clonedObj = Array.isArray(obj) ? [] : {};
105
+ for (let key in obj) {
106
+ if (obj.hasOwnProperty(key)) {
107
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
108
+ clonedObj[key] = deepClone(obj[key]); // Recursively clone nested objects
109
+ } else if (obj[key] instanceof Int16Array) {
110
+ clonedObj[key] = new Int16Array(obj[key]); // Clone Int16Array
111
+ } else {
112
+ clonedObj[key] = obj[key]; // Copy primitives
113
+ }
114
+ }
115
+ }
116
+ return clonedObj;
117
+ }
118
+
119
+
120
+ /**
121
+ * @param channel {number} a hint for the processor to recalculate sample cursors when sample dumping
122
+ * @param midiNote {number}
123
+ * @param velocity {number}
124
+ * @param preset {Preset}
125
+ * @param currentTime {number}
126
+ * @param sampleRate {number}
127
+ * @param sampleDumpCallback {function({channel: number, sampleID: number, sampleData: Float32Array})}
128
+ * @param cachedVoices {WorkletVoice[][][]} first is midi note, second is velocity. output is an array of WorkletVoices
129
+ * @param debug {boolean}
130
+ * @returns {WorkletVoice[]}
131
+ */
132
+ export function getWorkletVoices(channel,
133
+ midiNote,
134
+ velocity,
135
+ preset,
136
+ currentTime,
137
+ sampleRate,
138
+ sampleDumpCallback,
139
+ cachedVoices,
140
+ debug=false)
141
+ {
142
+ /**
143
+ * @type {WorkletVoice[]}
144
+ */
145
+ let workletVoices;
146
+
147
+ const cached = cachedVoices[midiNote][velocity];
148
+ if(cached !== undefined)
149
+ {
150
+ workletVoices = cached.map(deepClone);
151
+ workletVoices.forEach(v => {
152
+ v.startTime = currentTime;
153
+ });
154
+ }
155
+ else
156
+ {
157
+ /**
158
+ * @returns {WorkletVoice[]}
159
+ */
160
+ workletVoices = preset.getSamplesAndGenerators(midiNote, velocity).reduce((voices, sampleAndGenerators) => {
161
+ // dump the sample if haven't already
162
+ if (globalDumpedSamplesList[sampleAndGenerators.sampleID] !== true)
163
+ {
164
+ dumpSample(channel, sampleAndGenerators.sample, sampleAndGenerators.sampleID, sampleDumpCallback);
165
+ }
166
+ if(sampleAndGenerators.sample.sampleData === undefined)
167
+ {
168
+ SpessaSynthWarn(`Discarding invalid sample: ${sampleAndGenerators.sample.sampleName}`);
169
+ return voices;
170
+ }
171
+
172
+ // create the generator list
173
+ const generators = new Int16Array(60);
174
+ // apply and sum the gens
175
+ for (let i = 0; i < 60; i++)
176
+ {
177
+ generators[i] = addAndClampGenerator(i, sampleAndGenerators.presetGenerators, sampleAndGenerators.instrumentGenerators);
178
+ }
179
+
180
+ // !! EMU initial attenuation correction, multiply initial attenuation by 0.4
181
+ generators[generatorTypes.initialAttenuation] = Math.floor(generators[generatorTypes.initialAttenuation] * 0.4);
182
+
183
+ // key override
184
+ let rootKey = sampleAndGenerators.sample.samplePitch;
185
+ if (generators[generatorTypes.overridingRootKey] > -1) {
186
+ rootKey = generators[generatorTypes.overridingRootKey];
187
+ }
188
+
189
+ let targetKey = midiNote;
190
+ if (generators[generatorTypes.keyNum] > -1) {
191
+ targetKey = generators[generatorTypes.keyNum];
192
+ }
193
+
194
+ // determine looping mode now. if the loop is too small, disable
195
+ const loopStart = (sampleAndGenerators.sample.sampleLoopStartIndex / 2) + (generators[generatorTypes.startloopAddrsOffset] + (generators[generatorTypes.startloopAddrsCoarseOffset] * 32768));
196
+ const loopEnd = (sampleAndGenerators.sample.sampleLoopEndIndex / 2) + (generators[generatorTypes.endloopAddrsOffset] + (generators[generatorTypes.endloopAddrsCoarseOffset] * 32768));
197
+ let loopingMode = generators[generatorTypes.sampleModes];
198
+ if (loopEnd - loopStart < 1) {
199
+ loopingMode = 0;
200
+ }
201
+
202
+ // determine end
203
+ /**
204
+ * create the worklet sample
205
+ * @type {WorkletSample}
206
+ */
207
+ const workletSample = {
208
+ sampleID: sampleAndGenerators.sampleID,
209
+ playbackStep: (sampleAndGenerators.sample.sampleRate / sampleRate) * Math.pow(2, sampleAndGenerators.sample.samplePitchCorrection / 1200),// cent tuning
210
+ cursor: generators[generatorTypes.startAddrsOffset] + (generators[generatorTypes.startAddrsCoarseOffset] * 32768),
211
+ rootKey: rootKey,
212
+ loopStart: loopStart,
213
+ loopEnd: loopEnd,
214
+ end: Math.floor( sampleAndGenerators.sample.sampleData.length) - 1 + (generators[generatorTypes.endAddrOffset] + (generators[generatorTypes.endAddrsCoarseOffset] * 32768)),
215
+ loopingMode: loopingMode
216
+ };
217
+
218
+ // velocity override
219
+ if (generators[generatorTypes.velocity] > -1) {
220
+ velocity = generators[generatorTypes.velocity];
221
+ }
222
+
223
+ if(debug)
224
+ {
225
+ SpessaSynthTable([{
226
+ Sample: sampleAndGenerators.sample.sampleName,
227
+ Generators: generators,
228
+ Modulators: sampleAndGenerators.modulators.map(m => m.debugString()),
229
+ Velocity: velocity,
230
+ TargetKey: targetKey,
231
+ MidiNote: midiNote,
232
+ WorkletSample: workletSample
233
+ }]);
234
+ }
235
+
236
+
237
+ voices.push({
238
+ filter: deepClone(DEFAULT_WORKLET_LOWPASS_FILTER),
239
+ // generators and modulators
240
+ generators: generators,
241
+ modulators: sampleAndGenerators.modulators,
242
+ modulatedGenerators: new Int16Array(60),
243
+
244
+ // sample and playback data
245
+ sample: workletSample,
246
+ velocity: velocity,
247
+ midiNote: midiNote,
248
+ pressure: 0,
249
+ channelNumber: channel,
250
+ startTime: currentTime,
251
+ targetKey: targetKey,
252
+ currentTuningCalculated: 1,
253
+ currentTuningCents: 0,
254
+ releaseStartTime: Infinity,
255
+
256
+ // envelope data
257
+ finished: false,
258
+ isInRelease: false,
259
+ currentModEnvValue: 0,
260
+ releaseStartModEnv: 1,
261
+ currentPan: 0.5,
262
+
263
+ volumeEnvelope: deepClone(DEFAULT_WORKLET_VOLUME_ENVELOPE)
264
+ });
265
+ return voices;
266
+ }, []);
267
+ // cache the voice
268
+ // clone it so the system won't mess with it!
269
+ cachedVoices[midiNote][velocity] = workletVoices.map(deepClone);
270
+ }
271
+ return workletVoices;
272
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The absolute path (from the spessasynth_lib folder) to the worklet module
3
+ * @type {string}
4
+ */
5
+ export const WORKLET_URL_ABSOLUTE = "synthetizer/worklet_processor.min.js";
@@ -0,0 +1,4 @@
1
+ ## This is the utility folder.
2
+ There are various utilites here used by the SpessaSynth library.
3
+
4
+ ### Note that the stbvorbis_sync.js is licensed under Apache-2.0.
@@ -0,0 +1,101 @@
1
+ /**
2
+ *
3
+ * @param audioBuffer {AudioBuffer}
4
+ * @param normalizeAudio {boolean} find the max sample point and set it to 1, and scale others with it
5
+ * @param channelOffset {number} channel offset and channel offset + 1 get saved
6
+ * @returns {Blob}
7
+ */
8
+ export function audioBufferToWav(audioBuffer, normalizeAudio = true, channelOffset = 0)
9
+ {
10
+
11
+ const channel1Data = audioBuffer.getChannelData(channelOffset);
12
+ const channel2Data = audioBuffer.getChannelData(channelOffset + 1);
13
+ const length = channel1Data.length;
14
+
15
+ const bytesPerSample = 2; // 16-bit PCM
16
+
17
+ // Prepare the header
18
+ const headerSize = 44;
19
+ const dataSize = length * 2 * bytesPerSample; // 2 channels, 16-bit per channel
20
+ const fileSize = headerSize + dataSize - 8; // total file size minus the first 8 bytes
21
+ const header = new Uint8Array(headerSize);
22
+
23
+ // 'RIFF'
24
+ header.set([82, 73, 70, 70], 0);
25
+ // file length
26
+ header.set(new Uint8Array([fileSize & 0xff, (fileSize >> 8) & 0xff, (fileSize >> 16) & 0xff, (fileSize >> 24) & 0xff]), 4);
27
+ // 'WAVE'
28
+ header.set([87, 65, 86, 69], 8);
29
+ // 'fmt '
30
+ header.set([102, 109, 116, 32], 12);
31
+ // fmt chunk length
32
+ header.set([16, 0, 0, 0], 16); // 16 for PCM
33
+ // audio format (PCM)
34
+ header.set([1, 0], 20);
35
+ // number of channels (2)
36
+ header.set([2, 0], 22);
37
+ // sample rate
38
+ const sampleRate = audioBuffer.sampleRate;
39
+ header.set(new Uint8Array([sampleRate & 0xff, (sampleRate >> 8) & 0xff, (sampleRate >> 16) & 0xff, (sampleRate >> 24) & 0xff]), 24);
40
+ // byte rate (sample rate * block align)
41
+ const byteRate = sampleRate * 2 * bytesPerSample; // 2 channels, 16-bit per channel
42
+ header.set(new Uint8Array([byteRate & 0xff, (byteRate >> 8) & 0xff, (byteRate >> 16) & 0xff, (byteRate >> 24) & 0xff]), 28);
43
+ // block align (channels * bytes per sample)
44
+ header.set([4, 0], 32); // 2 channels * 16-bit per channel / 8
45
+ // bits per sample
46
+ header.set([16, 0], 34); // 16-bit
47
+
48
+ // data chunk identifier 'data'
49
+ header.set([100, 97, 116, 97], 36);
50
+ // data chunk length
51
+ header.set(new Uint8Array([dataSize & 0xff, (dataSize >> 8) & 0xff, (dataSize >> 16) & 0xff, (dataSize >> 24) & 0xff]), 40);
52
+
53
+ const wavData = new Uint8Array(headerSize + dataSize);
54
+ wavData.set(header, 0);
55
+ // Interleave audio data (combine channels)
56
+ let offset = headerSize;
57
+
58
+ let multiplier;
59
+ if(normalizeAudio)
60
+ {
61
+ // find min and max values to prevent clipping when converting to 16 bits
62
+ const initialMultiplier = 32767;
63
+
64
+ const max = Math.max(
65
+ channel1Data.reduce((max, value) => (value > max ? value : max), -Infinity),
66
+ channel2Data.reduce((max, value) => (value > max ? value : max), -Infinity)
67
+ );
68
+
69
+ const min = Math.min(
70
+ channel1Data.reduce((min, value) => (value < min ? value : min), Infinity),
71
+ channel2Data.reduce((min, value) => (value < min ? value : min), Infinity)
72
+ );
73
+ const maxAbsValue = Math.max(max, Math.abs(min));
74
+ multiplier = initialMultiplier / maxAbsValue;
75
+ }
76
+ else
77
+ {
78
+ multiplier = 32767;
79
+ // clip audio
80
+ for(let i = 0; i < length; i++)
81
+ {
82
+ channel1Data[i] = Math.min(1, Math.max(-1, channel1Data[i]));
83
+ channel2Data[i] = Math.min(1, Math.max(-1, channel2Data[i]));
84
+ }
85
+ }
86
+ for (let i = 0; i < length; i++)
87
+ {
88
+ // interleave both channels
89
+ const sample1 = channel1Data[i] * multiplier;
90
+ const sample2 = channel2Data[i] * multiplier;
91
+
92
+ // convert to 16-bit
93
+ wavData[offset++] = sample1 & 0xff;
94
+ wavData[offset++] = (sample1 >> 8) & 0xff;
95
+ wavData[offset++] = sample2 & 0xff;
96
+ wavData[offset++] = (sample2 >> 8) & 0xff;
97
+ }
98
+
99
+
100
+ return new Blob([wavData.buffer], { type: 'audio/wav' });
101
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Reads as Big endian
3
+ * @param dataArray {IndexedByteArray}
4
+ * @param bytesAmount {number}
5
+ * @returns {number}
6
+ */
7
+ export function readBytesAsUintBigEndian(dataArray, bytesAmount) {
8
+ let out = 0
9
+ for (let i = 8 * (bytesAmount - 1); i >= 0; i -= 8) {
10
+ out |= (dataArray[dataArray.currentIndex++] << i)
11
+ }
12
+ return out >>> 0
13
+ }
14
+
15
+ /**
16
+ * @param number {number}
17
+ * @param bytesAmount {number}
18
+ * @returns {number[]}
19
+ */
20
+ export function writeBytesAsUintBigEndian(number, bytesAmount) {
21
+ const bytes = new Array(bytesAmount).fill(0)
22
+ for (let i = bytesAmount - 1; i >= 0; i--) {
23
+ bytes[i] = number & 0xFF
24
+ number >>= 8
25
+ }
26
+
27
+ return bytes
28
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Reads as little endian
3
+ * @param dataArray {IndexedByteArray}
4
+ * @param bytesAmount {number}
5
+ * @returns {number}
6
+ */
7
+ export function readBytesAsUintLittleEndian(dataArray, bytesAmount){
8
+ let out = 0;
9
+ for(let i = 0; i < bytesAmount; i++)
10
+ {
11
+ out |= (dataArray[dataArray.currentIndex++] << i * 8);
12
+ }
13
+ // make sure it stays unsigned
14
+ return out >>> 0;
15
+ }
16
+
17
+ /**
18
+ * Writes a number as little endian seems to also work for negative numbers so yay?
19
+ * @param dataArray {IndexedByteArray}
20
+ * @param number {number}
21
+ * @param byteTarget {number}
22
+ */
23
+ export function writeLittleEndian(dataArray, number, byteTarget)
24
+ {
25
+ for(let i = 0; i < byteTarget; i++)
26
+ {
27
+ dataArray[dataArray.currentIndex++] = (number >> (i * 8)) & 0xFF;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @param dataArray {IndexedByteArray}
33
+ * @param word {number}
34
+ */
35
+ export function writeWord(dataArray, word)
36
+ {
37
+ dataArray[dataArray.currentIndex++] = word & 0xFF;
38
+ dataArray[dataArray.currentIndex++] = word >> 8;
39
+ }
40
+
41
+ /**
42
+ * @param dataArray {IndexedByteArray}
43
+ * @param dword {number}
44
+ */
45
+ export function writeDword(dataArray, dword)
46
+ {
47
+ writeLittleEndian(dataArray, dword, 4);
48
+ }
49
+
50
+ /**
51
+ * @param byte1 {number}
52
+ * @param byte2 {number}
53
+ * @returns {number}
54
+ */
55
+ export function signedInt16(byte1, byte2){
56
+ let val = (byte2 << 8) | byte1;
57
+ if(val > 32767)
58
+ {
59
+ return val - 65536;
60
+ }
61
+ return val;
62
+ }
63
+
64
+ /**
65
+ * @param byte {number}
66
+ * @returns {number}
67
+ */
68
+ export function signedInt8(byte) {
69
+ if(byte > 127)
70
+ {
71
+ return byte - 256;
72
+ }
73
+ return byte;
74
+ }