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.
- package/.idea/inspectionProfiles/Project_Default.xml +10 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/spessasynth_core.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +376 -0
- package/index.js +7 -0
- package/package.json +34 -0
- package/spessasynth_core/midi_parser/README.md +3 -0
- package/spessasynth_core/midi_parser/midi_loader.js +381 -0
- package/spessasynth_core/midi_parser/midi_message.js +231 -0
- package/spessasynth_core/sequencer/sequencer.js +192 -0
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
- package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
- package/spessasynth_core/soundfont/README.md +4 -0
- package/spessasynth_core/soundfont/chunk/generators.js +205 -0
- package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
- package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
- package/spessasynth_core/soundfont/chunk/presets.js +264 -0
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
- package/spessasynth_core/soundfont/chunk/samples.js +250 -0
- package/spessasynth_core/soundfont/chunk/zones.js +264 -0
- package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
- package/spessasynth_core/synthetizer/README.md +6 -0
- package/spessasynth_core/synthetizer/synthesizer.js +303 -0
- package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
- package/spessasynth_core/utils/README.md +4 -0
- package/spessasynth_core/utils/buffer_to_wav.js +70 -0
- package/spessasynth_core/utils/byte_functions.js +141 -0
- package/spessasynth_core/utils/loggin.js +79 -0
- package/spessasynth_core/utils/other.js +49 -0
- package/spessasynth_core/utils/shiftable_array.js +26 -0
- 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,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
|
+
}
|