spessasynth_core 1.0.0

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 (55) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  2. package/.idea/jsLibraryMappings.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/spessasynth_core.iml +12 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README.md +376 -0
  7. package/index.js +7 -0
  8. package/package.json +34 -0
  9. package/spessasynth_core/midi_parser/README.md +3 -0
  10. package/spessasynth_core/midi_parser/midi_loader.js +381 -0
  11. package/spessasynth_core/midi_parser/midi_message.js +231 -0
  12. package/spessasynth_core/sequencer/sequencer.js +192 -0
  13. package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
  14. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
  15. package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
  16. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
  17. package/spessasynth_core/soundfont/README.md +4 -0
  18. package/spessasynth_core/soundfont/chunk/generators.js +205 -0
  19. package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
  20. package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
  21. package/spessasynth_core/soundfont/chunk/presets.js +264 -0
  22. package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
  23. package/spessasynth_core/soundfont/chunk/samples.js +250 -0
  24. package/spessasynth_core/soundfont/chunk/zones.js +264 -0
  25. package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
  26. package/spessasynth_core/synthetizer/README.md +6 -0
  27. package/spessasynth_core/synthetizer/synthesizer.js +303 -0
  28. package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
  29. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
  30. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
  31. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
  32. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
  33. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
  34. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
  35. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
  36. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  37. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
  38. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  39. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
  40. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  41. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  42. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
  43. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  44. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
  45. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  46. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
  47. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
  48. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
  49. package/spessasynth_core/utils/README.md +4 -0
  50. package/spessasynth_core/utils/buffer_to_wav.js +70 -0
  51. package/spessasynth_core/utils/byte_functions.js +141 -0
  52. package/spessasynth_core/utils/loggin.js +79 -0
  53. package/spessasynth_core/utils/other.js +49 -0
  54. package/spessasynth_core/utils/shiftable_array.js +26 -0
  55. package/spessasynth_core/utils/stbvorbis_sync.js +1877 -0
@@ -0,0 +1,90 @@
1
+ import { consoleColors, formatTime } from '../../utils/other.js'
2
+ import { SpessaSynthInfo, SpessaSynthWarn } from '../../utils/loggin.js'
3
+
4
+ /**
5
+ * Loads a new sequence
6
+ * @param parsedMidi {MIDI}
7
+ * @this {Sequencer}
8
+ */
9
+ export function loadNewSequence(parsedMidi)
10
+ {
11
+ this.stop();
12
+ if (!parsedMidi.tracks) {
13
+ throw "No tracks supplied!";
14
+ }
15
+
16
+ this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision)
17
+
18
+ /**
19
+ * @type {MIDI}
20
+ */
21
+ this.midiData = parsedMidi;
22
+
23
+ /**
24
+ * merge the tracks
25
+ * @type {MidiMessage[]}
26
+ */
27
+ //this.events = this.midiData.tracks.flat();
28
+ //this.events.sort((e1, e2) => e1.ticks - e2.ticks);
29
+
30
+ /**
31
+ * the midi track data
32
+ * @type {MidiMessage[][]}
33
+ */
34
+ this.tracks = this.midiData.tracks;
35
+
36
+ // copy over the port data (can be overwritten in real time if needed)
37
+ this.midiPorts = this.midiData.midiPorts;
38
+
39
+ /**
40
+ * Same as Audio.duration (seconds)
41
+ * @type {number}
42
+ */
43
+ this.duration = this.midiData.duration;
44
+ SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
45
+ this.midiPortChannelOffset = 0;
46
+ this.midiPortChannelOffsets = {};
47
+
48
+ this.synth.resetAllControllers();
49
+ if(this.duration <= 1)
50
+ {
51
+ SpessaSynthWarn(`%cVery short song: (${formatTime(Math.round(this.duration)).time}). Disabling loop!`,
52
+ consoleColors.warn);
53
+ this.loop = false;
54
+ }
55
+ this.play(true);
56
+ }
57
+
58
+ /**
59
+ * @param parsedMidis {MIDI[]}
60
+ * @this {Sequencer}
61
+ */
62
+ export function loadNewSongList(parsedMidis)
63
+ {
64
+ this.songs = parsedMidis;
65
+ this.songIndex = 0;
66
+ this.loadNewSequence(this.songs[this.songIndex]);
67
+ }
68
+
69
+ /**
70
+ * @this {Sequencer}
71
+ */
72
+ export function nextSong()
73
+ {
74
+ this.songIndex++;
75
+ this.songIndex %= this.songs.length;
76
+ this.loadNewSequence(this.songs[this.songIndex]);
77
+ }
78
+
79
+ /**
80
+ * @this {Sequencer}
81
+ */
82
+ export function previousSong()
83
+ {
84
+ this.songIndex--;
85
+ if(this.songIndex < 0)
86
+ {
87
+ this.songIndex = this.songs.length - 1;
88
+ }
89
+ this.loadNewSequence(this.songs[this.songIndex]);
90
+ }
@@ -0,0 +1,4 @@
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`)
@@ -0,0 +1,205 @@
1
+ import { ShiftableByteArray } from '../../utils/shiftable_array.js'
2
+ import { RiffChunk } from './riff_chunk.js'
3
+ import { readByte, signedInt16 } from '../../utils/byte_functions.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
+ export const generatorTypes = {
11
+ startAddrsOffset: 0, // sample control - moves sample start point
12
+ endAddrOffset: 1, // sample control - moves sample end point
13
+ startloopAddrsOffset: 2, // loop control - moves loop start point
14
+ endloopAddrsOffset: 3, // loop control - moves loop end point
15
+ startAddrsCoarseOffset: 4, // ?
16
+ modLfoToPitch: 5, // pitch modulation - modulation lfo pitch modulation in cents
17
+ vibLfoToPitch: 6, // pitch modulation - vibrato lfo pitch modulation in cents
18
+ modEnvToPitch: 7, // pitch modulation - modulation envelope pitch modulation in cents
19
+ initialFilterFc: 8, // filter - lowpass filter cutoff in cents
20
+ initialFilterQ: 9, // filter - lowpass filter resonance
21
+ modLfoToFilterFc: 10, // filter modulation - modulation lfo lowpass filter cutoff in cents
22
+ modEnvToFilterFc: 11, // filter modulation - modulation envelope lowpass filter cutoff in cents
23
+ endAddrsCoarseOffset: 12, // ?
24
+ modLfoToVolume: 13, // modulation lfo - volume (tremolo), where 100 = 10dB
25
+ unused1: 14,
26
+ chorusEffectsSend: 15, // effect send - how much is sent to chorus 0 - 1000
27
+ reverbEffectsSend: 16, // effect send - how much is sent to reverb 0 - 1000
28
+ pan: 17, // panning - where -500 = left, 0 = center, 500 = right
29
+ unused2: 18,
30
+ unused3: 19,
31
+ unused4: 20,
32
+ delayModLFO: 21, // mod lfo - delay for mod lfo to start from zero (weird scale)
33
+ freqModLFO: 22, // mod lfo - frequency of mod lfo, 0 = 8.176Hz, unit: f => 1200log2(f/8.176)
34
+ delayVibLFO: 23, // vib lfo - delay for vibrato lfo to start from zero (weird scale)
35
+ freqVibLFO: 24, // vib lfo - frequency of vibrato lfo, 0 = 8.176Hz, unit: f => 1200log2(f/8.176)
36
+ delayModEnv: 25, // mod env - 0 = 1s declay till mod env starts
37
+ attackModEnv: 26, // mod env - attack of mod env
38
+ holdModEnv: 27, // mod env - hold of mod env
39
+ decayModEnv: 28, // mod env - decay of mod env
40
+ sustainModEnv: 29, // mod env - sustain of mod env
41
+ releaseModEnv: 30, // mod env - release of mod env
42
+ keyNumToModEnvHold: 31, // mod env - also modulating mod envelope hold with key number
43
+ keyNumToModEnvDecay: 32, // mod env - also modulating mod envelope decay with key number
44
+ delayVolEnv: 33, // vol env - delay of envelope from zero (weird scale)
45
+ attackVolEnv: 34, // vol env - attack of envelope
46
+ holdVolEnv: 35, // vol env - hold of envelope
47
+ decayVolEnv: 36, // vol env - decay of envelope
48
+ sustainVolEnv: 37, // vol env - sustain of envelope
49
+ releaseVolEnv: 38, // vol env - release of envelope
50
+ keyNumToVolEnvHold: 39, // vol env - key number to volume envelope hold
51
+ keyNumToVolEnvDecay: 40, // vol env - key number to volume envelope decay
52
+ instrument: 41, // zone - instrument index to use for preset zone
53
+ reserved1: 42,
54
+ keyRange: 43, // zone - key range for which preset / instrument zone is active
55
+ velRange: 44, // zone - velocity range for which preset / instrument zone is active
56
+ startloopAddrsCoarseOffset: 45, // ?
57
+ keyNum: 46, // zone - instrument only: always use this midi number (ignore what's pressed)
58
+ velocity: 47, // zone - instrument only: always use this velocity (ignore what's pressed)
59
+ initialAttenuation: 48, // zone - allows turning down the volume, 10 = -1dB
60
+ reserved2: 49,
61
+ endloopAddrsCoarseOffset: 50, // ?
62
+ coarseTune: 51, // tune - pitch offset in semitones
63
+ fineTune: 52, // tune - pitch offset in cents
64
+ sampleID: 53, // sample - instrument zone only: which sample to use
65
+ sampleModes: 54, // sample - 0 = no loop, 1 = loop, 2 = reserved, 3 = loop and play till end in release phase
66
+ reserved3: 55,
67
+ scaleTuning: 56, // sample - the degree to which MIDI key number influences pitch, 100 = default
68
+ exclusiveClass: 57, // sample - = cut = choke group
69
+ overridingRootKey: 58, // sample - can override the sample's original pitch
70
+ unused5: 59,
71
+ endOper: 60 // end marker
72
+ };
73
+
74
+ /**
75
+ * @type {{min: number, max: number, def: number}[]}
76
+ */
77
+ export const generatorLimits = [];
78
+ // offsets
79
+ generatorLimits[generatorTypes.startAddrsOffset] = {min: 0, max: 32768, def: 0};
80
+ generatorLimits[generatorTypes.endAddrOffset] = {min: -32768, max: 32768, def: 0};
81
+ generatorLimits[generatorTypes.startloopAddrsOffset] = {min: -32768, max: 32768, def: 0};
82
+ generatorLimits[generatorTypes.endloopAddrsOffset] = {min: -32768, max: 32768, def: 0};
83
+ generatorLimits[generatorTypes.startAddrsCoarseOffset] = {min: 0, max: 32768, def: 0};
84
+
85
+ // pitch influence
86
+ generatorLimits[generatorTypes.modLfoToPitch] = {min: -12000, max: 12000, def: 0};
87
+ generatorLimits[generatorTypes.vibLfoToPitch] = {min: -12000, max: 12000, def: 0};
88
+ generatorLimits[generatorTypes.modEnvToPitch] = {min: -12000, max: 12000, def: 0};
89
+
90
+ // lowpass
91
+ generatorLimits[generatorTypes.initialFilterFc] = {min: 1500, max: 13500, def: 13500};
92
+ generatorLimits[generatorTypes.initialFilterQ] = {min: 0, max: 960, def: 0};
93
+ generatorLimits[generatorTypes.modLfoToFilterFc] = {min: -12000, max: 12000, def: 0};
94
+ generatorLimits[generatorTypes.modEnvToFilterFc] = {min: -12000, max: 12000, def: 0};
95
+
96
+ generatorLimits[generatorTypes.endAddrsCoarseOffset] = {min: -32768, max: 32768, def: 0};
97
+
98
+ generatorLimits[generatorTypes.modLfoToVolume] = {min: -960, max: 960, def: 0};
99
+
100
+ // effects, pan
101
+ generatorLimits[generatorTypes.chorusEffectsSend] = {min: 0, max: 1000, def: 0};
102
+ generatorLimits[generatorTypes.reverbEffectsSend] = {min: 0, max: 1000, def: 0};
103
+ generatorLimits[generatorTypes.pan] = {min: -500, max: 500, def: 0};
104
+
105
+ // lfo
106
+ generatorLimits[generatorTypes.delayModLFO] = {min: -12000, max: 5000, def: -12000};
107
+ generatorLimits[generatorTypes.freqModLFO] = {min: -16000, max: 4500, def: 0};
108
+ generatorLimits[generatorTypes.delayVibLFO] = {min: -12000, max: 5000, def: -12000};
109
+ generatorLimits[generatorTypes.freqVibLFO] = {min: -16000, max: 4500, def: 0};
110
+
111
+ // mod env
112
+ generatorLimits[generatorTypes.delayModEnv] = {min: -12000, max: 5000, def: -12000};
113
+ generatorLimits[generatorTypes.attackModEnv] = {min: -12000, max: 8000, def: -12000};
114
+ generatorLimits[generatorTypes.holdModEnv] = {min: -12000, max: 5000, def: -12000};
115
+ generatorLimits[generatorTypes.decayModEnv] = {min: -12000, max: 8000, def: -12000};
116
+ generatorLimits[generatorTypes.sustainModEnv] = {min: 0, max: 1000, def: 0};
117
+ generatorLimits[generatorTypes.releaseModEnv] = {min: -12000, max: 8000, def: -12000};
118
+ // keynum to mod env
119
+ generatorLimits[generatorTypes.keyNumToModEnvHold] = {min: -1200, max: 1200, def: 0};
120
+ generatorLimits[generatorTypes.keyNumToModEnvDecay] = {min: -1200, max: 1200, def: 0};
121
+
122
+ // vol env
123
+ generatorLimits[generatorTypes.delayVolEnv] = {min: -12000, max: 5000, def: -12000};
124
+ generatorLimits[generatorTypes.attackVolEnv] = {min: -12000, max: 8000, def: -12000};
125
+ generatorLimits[generatorTypes.holdVolEnv] = {min: -12000, max: 5000, def: -12000};
126
+ generatorLimits[generatorTypes.decayVolEnv] = {min: -12000, max: 8000, def: -12000};
127
+ generatorLimits[generatorTypes.sustainVolEnv] = {min: 0, max: 1440, def: 0};
128
+ generatorLimits[generatorTypes.releaseVolEnv] = {min: -7200, max: 8000, def: -12000}; // prevent clicks
129
+ // keynum to vol env
130
+ generatorLimits[generatorTypes.keyNumToVolEnvHold] = {min: -1200, max: 1200, def: 0};
131
+ generatorLimits[generatorTypes.keyNumToVolEnvDecay] = {min: -1200, max: 1200, def: 0};
132
+
133
+ generatorLimits[generatorTypes.startloopAddrsCoarseOffset] = {min: -32768, max: 32768, def: 0};
134
+ generatorLimits[generatorTypes.keyNum] = {min: -1, max: 127, def: -1};
135
+ generatorLimits[generatorTypes.velocity] = {min: -1, max: 127, def: -1};
136
+
137
+ generatorLimits[generatorTypes.initialAttenuation] = {min: -250, max: 1440, def: 0}; // soundblaster allows 10dB of gain
138
+
139
+ generatorLimits[generatorTypes.endloopAddrsCoarseOffset] = {min: -32768, max: 32768, def: 0};
140
+
141
+ generatorLimits[generatorTypes.coarseTune] = {min: -120, max: 120, def: 0};
142
+ generatorLimits[generatorTypes.fineTune] = {min: -99, max: 99, def: 0};
143
+ generatorLimits[generatorTypes.scaleTuning] = {min: 0, max: 1200, def: 100};
144
+ generatorLimits[generatorTypes.exclusiveClass] = {min: 0, max: 99999, def: 0};
145
+ generatorLimits[generatorTypes.overridingRootKey] = {min: 0-1, max: 127, def: -1};
146
+
147
+
148
+ /**
149
+ * @param generatorType {number}
150
+ * @param presetGens {Generator[]}
151
+ * @param instrumentGens {Generator[]}
152
+ */
153
+ export function addAndClampGenerator(generatorType, presetGens, instrumentGens)
154
+ {
155
+ const limits = generatorLimits[generatorType] || {min: 0, max: 32768, def: 0};
156
+ let presetGen = presetGens.find(g => g.generatorType === generatorType);
157
+ let presetValue = 0;
158
+ if(presetGen)
159
+ {
160
+ presetValue = presetGen.generatorValue;
161
+ }
162
+
163
+ let instruGen = instrumentGens.find(g => g.generatorType === generatorType);
164
+ let instruValue = limits.def;
165
+ if(instruGen)
166
+ {
167
+ instruValue = instruGen.generatorValue;
168
+ }
169
+ return Math.max(limits.min, Math.min(limits.max, instruValue + presetValue));
170
+ }
171
+
172
+
173
+ export class Generator{
174
+ /**
175
+ * Creates a generator
176
+ * @param dataArray {ShiftableByteArray}
177
+ */
178
+ constructor(dataArray) {
179
+ // 4 bytes:
180
+ // type, value, type, value
181
+ let bytes = [readByte(dataArray), readByte(dataArray), readByte(dataArray), readByte(dataArray)];
182
+
183
+ /**
184
+ * @type {number}
185
+ **/
186
+ this.generatorType = (bytes[1] << 8) | bytes[0];
187
+
188
+ this.generatorValue = signedInt16(bytes[2], bytes[3]);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Reads the generator chunk
194
+ * @param generatorChunk {RiffChunk}
195
+ * @returns {Generator[]}
196
+ */
197
+ export function readGenerators(generatorChunk)
198
+ {
199
+ let gens = [];
200
+ while(generatorChunk.chunkData.length > generatorChunk.chunkData.currentIndex)
201
+ {
202
+ gens.push(new Generator(generatorChunk.chunkData));
203
+ }
204
+ return gens;
205
+ }
@@ -0,0 +1,60 @@
1
+ import {RiffChunk} from "./riff_chunk.js";
2
+ import {InstrumentZone} from "./zones.js";
3
+ import {readBytesAsString, readBytesAsUintLittleEndian} from "../../utils/byte_functions.js";
4
+
5
+ /**
6
+ * instrument.js
7
+ * purpose: parses soundfont instrument and stores them as a class
8
+ */
9
+
10
+ export class Instrument{
11
+ /**
12
+ * Creates an instrument
13
+ * @param instrumentChunk {RiffChunk}
14
+ */
15
+ constructor(instrumentChunk) {
16
+ this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20).trim();
17
+ this.instrumentZoneIndex = readBytesAsUintLittleEndian(instrumentChunk.chunkData, 2);
18
+ this.instrumentZonesAmount = 0;
19
+ /**
20
+ * @type {InstrumentZone[]}
21
+ */
22
+ this.instrumentZones = [];
23
+ }
24
+
25
+ /**
26
+ * Loads all the instrument zones, given the amount
27
+ * @param amount {number}
28
+ * @param zones {InstrumentZone[]}
29
+ */
30
+ getInstrumentZones(amount, zones)
31
+ {
32
+ this.instrumentZonesAmount = amount;
33
+ for(let i = this.instrumentZoneIndex; i < this.instrumentZonesAmount + this.instrumentZoneIndex; i++)
34
+ {
35
+ this.instrumentZones.push(zones[i]);
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Reads the instruments
42
+ * @param instrumentChunk {RiffChunk}
43
+ * @param instrumentZones {InstrumentZone[]}
44
+ * @returns {Instrument[]}
45
+ */
46
+ export function readInstruments(instrumentChunk, instrumentZones)
47
+ {
48
+ let instruments = [];
49
+ while(instrumentChunk.chunkData.length > instrumentChunk.chunkData.currentIndex)
50
+ {
51
+ let instrument = new Instrument(instrumentChunk);
52
+ if(instruments.length > 0)
53
+ {
54
+ let instrumentsAmount = instrument.instrumentZoneIndex - instruments[instruments.length - 1].instrumentZoneIndex;
55
+ instruments[instruments.length - 1].getInstrumentZones(instrumentsAmount, instrumentZones);
56
+ }
57
+ instruments.push(instrument);
58
+ }
59
+ return instruments;
60
+ }
@@ -0,0 +1,232 @@
1
+ import {signedInt16, readByte, readBytesAsUintLittleEndian} from "../../utils/byte_functions.js";
2
+ import { ShiftableByteArray } from '../../utils/shiftable_array.js';
3
+ import { generatorTypes } from './generators.js'
4
+ import { midiControllers } from '../../midi_parser/midi_message.js'
5
+
6
+ /**
7
+ * modulators.js
8
+ * purpose: parses soundfont modulators and the source enums, also includes the default modulators list
9
+ **/
10
+ export const modulatorSources = {
11
+ noController: 0,
12
+ noteOnVelocity: 2,
13
+ noteOnKeyNum: 3,
14
+ polyPressure: 10,
15
+ channelPressure: 13,
16
+ pitchWheel: 14,
17
+ pitchWheelRange: 16,
18
+ link: 127
19
+ }
20
+
21
+ export const modulatorCurveTypes = {
22
+ linear: 0,
23
+ concave: 1,
24
+ convex: 2,
25
+ switch: 3
26
+ }
27
+
28
+ /**
29
+ *
30
+ * type, polarity, direction
31
+ * @type {Float32Array[][][]}
32
+ */
33
+ export const precomputedTransforms = [];
34
+ for (let i = 0; i < 4; i++) {
35
+ precomputedTransforms.push([[], []]);
36
+ }
37
+
38
+ export class Modulator{
39
+ /**
40
+ * Creates a modulator
41
+ * @param dataArray {ShiftableByteArray|{srcEnum: number, secSrcEnum: number, dest:number, amt: number, transform: number}}
42
+ */
43
+ constructor(dataArray) {
44
+ if(dataArray.srcEnum)
45
+ {
46
+ this.modulatorSource = dataArray.srcEnum;
47
+ this.modulatorDestination = dataArray.dest;
48
+ this.modulationSecondarySrc = dataArray.secSrcEnum;
49
+ this.transformAmount = dataArray.amt;
50
+ this.transformType = dataArray.transform;
51
+ }
52
+ else {
53
+ this.modulatorSource = readBytesAsUintLittleEndian(dataArray, 2);
54
+ this.modulatorDestination = readBytesAsUintLittleEndian(dataArray, 2);
55
+ this.transformAmount = signedInt16(readByte(dataArray), readByte(dataArray));
56
+ this.modulationSecondarySrc = readBytesAsUintLittleEndian(dataArray, 2);
57
+ this.transformType = readBytesAsUintLittleEndian(dataArray, 2);
58
+ }
59
+
60
+ if(this.modulatorDestination > 58)
61
+ {
62
+ this.modulatorDestination = -1; // flag as invalid (for linked ones)
63
+ }
64
+
65
+ // decode the source
66
+ this.sourcePolarity = this.modulatorSource >> 9 & 1;
67
+ this.sourceDirection = this.modulatorSource >> 8 & 1;
68
+ this.sourceUsesCC = this.modulatorSource >> 7 & 1;
69
+ this.sourceIndex = this.modulatorSource & 127;
70
+ this.sourceCurveType = this.modulatorSource >> 10 & 3;
71
+
72
+ // decode the secondary source
73
+ this.secSrcPolarity = this.modulationSecondarySrc >> 9 & 1;
74
+ this.secSrcDirection = this.modulationSecondarySrc >> 8 & 1;
75
+ this.secSrcUsesCC = this.modulationSecondarySrc >> 7 & 1;
76
+ this.secSrcIndex = this.modulationSecondarySrc & 127;
77
+ this.secSrcCurveType = this.modulationSecondarySrc >> 10 & 3;
78
+
79
+ //this.precomputeModulatorTransform();
80
+ }
81
+
82
+ /**
83
+ * Sums transform and creates a NEW modulator
84
+ * @param modulator {Modulator}
85
+ * @returns {Modulator}
86
+ */
87
+ sumTransform(modulator)
88
+ {
89
+ return new Modulator({
90
+ srcEnum: this.modulatorSource,
91
+ secSrcEnum: this.modulationSecondarySrc,
92
+ dest: this.modulatorDestination,
93
+ transform: this.transformType,
94
+ amt: this.transformAmount + modulator.transformAmount
95
+ });
96
+ }
97
+
98
+ /**
99
+ * @returns {string}
100
+ */
101
+ debugString()
102
+ {
103
+ function getKeyByValue(object, value)
104
+ {
105
+ return Object.keys(object).find(key => object[key] === value);
106
+ }
107
+
108
+ let sourceString = getKeyByValue(modulatorCurveTypes, this.sourceCurveType);
109
+ sourceString += this.sourcePolarity === 0 ? " unipolar " : " bipolar ";
110
+ sourceString += this.sourceDirection === 0 ? "forwards " : "backwards ";
111
+ if(this.sourceUsesCC)
112
+ {
113
+ sourceString += getKeyByValue(midiControllers, this.sourceIndex);
114
+ }
115
+ else
116
+ {
117
+ sourceString += getKeyByValue(modulatorSources, this.sourceIndex);
118
+ }
119
+
120
+ let secSrcString = getKeyByValue(modulatorCurveTypes, this.secSrcCurveType);
121
+ secSrcString += this.secSrcPolarity === 0 ? " unipolar " : " bipolar ";
122
+ secSrcString += this.secSrcCurveType === 0 ? "forwards " : "backwards ";
123
+ if(this.secSrcUsesCC)
124
+ {
125
+ secSrcString += getKeyByValue(midiControllers, this.secSrcIndex);
126
+ }
127
+ else
128
+ {
129
+ secSrcString += getKeyByValue(modulatorSources, this.secSrcIndex);
130
+ }
131
+ return `Modulator:
132
+ Source: ${sourceString}
133
+ Secondary source: ${secSrcString}
134
+ Destination: ${getKeyByValue(generatorTypes, this.modulatorDestination)}
135
+ Trasform amount: ${this.transformAmount}
136
+ Transform type: ${this.transformType}
137
+ \n\n`;
138
+ }
139
+ }
140
+
141
+ function getModSourceEnum(curveType, polarity, direction, isCC, index)
142
+ {
143
+ return (curveType << 10) | (polarity << 9) | (direction << 8) | (isCC << 7) | index;
144
+ }
145
+
146
+ const DEFAULT_ATTENUATION_MOD_AMOUNT = 960;
147
+ const DEFAULT_ATTENUATION_MOD_CURVE_TYPE = modulatorCurveTypes.concave;
148
+
149
+ export const defaultModulators = [
150
+ // vel to attenuation
151
+ new Modulator({
152
+ srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 0, modulatorSources.noteOnVelocity),
153
+ dest: generatorTypes.initialAttenuation,
154
+ amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
155
+ secSrcEnum: 0x0,
156
+ transform: 0}),
157
+
158
+ // mod wheel to vibrato
159
+ new Modulator({srcEnum: 0x0081, dest: generatorTypes.vibLfoToPitch, amt: 50, secSrcEnum: 0x0, transform: 0}),
160
+
161
+ // vol to attenuation
162
+ new Modulator({
163
+ srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 1, midiControllers.mainVolume),
164
+ dest: generatorTypes.initialAttenuation,
165
+ amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
166
+ secSrcEnum: 0x0,
167
+ transform: 0}),
168
+
169
+ // pitch wheel to tuning
170
+ new Modulator({srcEnum: 0x020E, dest: generatorTypes.fineTune, amt: 12700, secSrcEnum: 0x0010, transform: 0}),
171
+
172
+ // pan to uhh, pan
173
+ new Modulator({srcEnum: 0x028A, dest: generatorTypes.pan, amt: 1000, secSrcEnum: 0x0, transform: 0}),
174
+
175
+ // expression to attenuation
176
+ new Modulator({
177
+ srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 1, midiControllers.expressionController),
178
+ dest: generatorTypes.initialAttenuation,
179
+ amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
180
+ secSrcEnum: 0x0,
181
+ transform: 0}),
182
+
183
+ // reverb effects to send
184
+ // 1000 to align with the reverbSend (overriding it works anyways)
185
+ new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
186
+
187
+ // chorus effects to send
188
+ new Modulator({srcEnum: 0x00DD, dest: generatorTypes.chorusEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
189
+
190
+ // custom modulators heck yeah
191
+ // cc 92 (tremolo) to modLFO volume
192
+ new Modulator({
193
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 0, 0, 1, midiControllers.effects2Depth), /*linear forward unipolar cc 92 */
194
+ dest: generatorTypes.modLfoToVolume,
195
+ amt: 24,
196
+ secSrcEnum: 0x0, // no controller
197
+ transform: 0
198
+ }),
199
+
200
+ // cc 72 (release time) to volEnv release
201
+ new Modulator({
202
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0, 1, midiControllers.releaseTime), // linear forward bipolar cc 72
203
+ dest: generatorTypes.releaseVolEnv,
204
+ amt: 1200,
205
+ secSrcEnum: 0x0, // no controller
206
+ transform: 0
207
+ }),
208
+
209
+ // cc 74 (brightness) to filterFc
210
+ new Modulator({
211
+ srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0 , 1, midiControllers.brightness), // linear forwards bipolar cc 74
212
+ dest: generatorTypes.initialFilterFc,
213
+ amt: 4000,
214
+ secSrcEnum: 0x0, // no controller
215
+ transform: 0
216
+ })
217
+ ];
218
+
219
+ /**
220
+ * Reads the modulator chunk
221
+ * @param modulatorChunk {RiffChunk}
222
+ * @returns {Modulator[]}
223
+ */
224
+ export function readModulators(modulatorChunk)
225
+ {
226
+ let gens = [];
227
+ while(modulatorChunk.chunkData.length > modulatorChunk.chunkData.currentIndex)
228
+ {
229
+ gens.push(new Modulator(modulatorChunk.chunkData));
230
+ }
231
+ return gens;
232
+ }