spessasynth_lib 3.9.9
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/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
- package/@types/index.d.ts +34 -0
- package/@types/midi_handler/midi_handler.d.ts +39 -0
- package/@types/midi_handler/web_midi_link.d.ts +12 -0
- package/@types/midi_parser/midi_data.d.ts +95 -0
- package/@types/midi_parser/midi_editor.d.ts +45 -0
- package/@types/midi_parser/midi_loader.d.ts +100 -0
- package/@types/midi_parser/midi_message.d.ts +154 -0
- package/@types/midi_parser/midi_writer.d.ts +6 -0
- package/@types/midi_parser/rmidi_writer.d.ts +9 -0
- package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
- package/@types/sequencer/sequencer.d.ts +180 -0
- package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
- package/@types/soundfont/read/generators.d.ts +98 -0
- package/@types/soundfont/read/instruments.d.ts +50 -0
- package/@types/soundfont/read/modulators.d.ts +73 -0
- package/@types/soundfont/read/presets.d.ts +87 -0
- package/@types/soundfont/read/riff_chunk.d.ts +31 -0
- package/@types/soundfont/read/samples.d.ts +134 -0
- package/@types/soundfont/read/zones.d.ts +141 -0
- package/@types/soundfont/soundfont.d.ts +76 -0
- package/@types/soundfont/write/ibag.d.ts +6 -0
- package/@types/soundfont/write/igen.d.ts +6 -0
- package/@types/soundfont/write/imod.d.ts +6 -0
- package/@types/soundfont/write/inst.d.ts +6 -0
- package/@types/soundfont/write/pbag.d.ts +6 -0
- package/@types/soundfont/write/pgen.d.ts +6 -0
- package/@types/soundfont/write/phdr.d.ts +6 -0
- package/@types/soundfont/write/pmod.d.ts +6 -0
- package/@types/soundfont/write/sdta.d.ts +11 -0
- package/@types/soundfont/write/shdr.d.ts +8 -0
- package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
- package/@types/soundfont/write/write.d.ts +21 -0
- package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
- package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
- package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
- package/@types/synthetizer/synth_event_handler.d.ts +161 -0
- package/@types/synthetizer/synthetizer.d.ts +286 -0
- package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +88 -0
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
- package/@types/synthetizer/worklet_url.d.ts +5 -0
- package/@types/utils/buffer_to_wav.d.ts +8 -0
- package/@types/utils/byte_functions/big_endian.d.ts +13 -0
- package/@types/utils/byte_functions/little_endian.d.ts +35 -0
- package/@types/utils/byte_functions/string.d.ts +22 -0
- package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
- package/@types/utils/indexed_array.d.ts +21 -0
- package/@types/utils/loggin.d.ts +26 -0
- package/@types/utils/other.d.ts +32 -0
- package/LICENSE +26 -0
- package/README.md +83 -0
- package/externals/NOTICE +9 -0
- package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
- package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
- package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/externals/stbvorbis_sync/LICENSE +202 -0
- package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/index.js +73 -0
- package/midi_handler/README.md +3 -0
- package/midi_handler/midi_handler.js +118 -0
- package/midi_handler/web_midi_link.js +41 -0
- package/midi_parser/README.md +3 -0
- package/midi_parser/midi_data.js +121 -0
- package/midi_parser/midi_editor.js +557 -0
- package/midi_parser/midi_loader.js +502 -0
- package/midi_parser/midi_message.js +234 -0
- package/midi_parser/midi_writer.js +95 -0
- package/midi_parser/rmidi_writer.js +271 -0
- package/midi_parser/used_keys_loaded.js +172 -0
- package/package.json +44 -0
- package/sequencer/README.md +23 -0
- package/sequencer/sequencer.js +439 -0
- package/sequencer/worklet_sequencer/events.js +92 -0
- package/sequencer/worklet_sequencer/play.js +309 -0
- package/sequencer/worklet_sequencer/process_event.js +167 -0
- package/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
- package/sequencer/worklet_sequencer/song_control.js +193 -0
- package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
- package/soundfont/README.md +8 -0
- package/soundfont/read/generators.js +212 -0
- package/soundfont/read/instruments.js +125 -0
- package/soundfont/read/modulators.js +249 -0
- package/soundfont/read/presets.js +300 -0
- package/soundfont/read/riff_chunk.js +81 -0
- package/soundfont/read/samples.js +398 -0
- package/soundfont/read/zones.js +310 -0
- package/soundfont/soundfont.js +357 -0
- package/soundfont/write/ibag.js +39 -0
- package/soundfont/write/igen.js +75 -0
- package/soundfont/write/imod.js +46 -0
- package/soundfont/write/inst.js +34 -0
- package/soundfont/write/pbag.js +39 -0
- package/soundfont/write/pgen.js +77 -0
- package/soundfont/write/phdr.js +42 -0
- package/soundfont/write/pmod.js +46 -0
- package/soundfont/write/sdta.js +72 -0
- package/soundfont/write/shdr.js +54 -0
- package/soundfont/write/soundfont_trimmer.js +169 -0
- package/soundfont/write/write.js +180 -0
- package/synthetizer/README.md +6 -0
- package/synthetizer/audio_effects/effects_config.js +21 -0
- package/synthetizer/audio_effects/fancy_chorus.js +120 -0
- package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/synthetizer/audio_effects/reverb.js +24 -0
- package/synthetizer/synth_event_handler.js +156 -0
- package/synthetizer/synthetizer.js +750 -0
- package/synthetizer/worklet_processor.min.js +13 -0
- package/synthetizer/worklet_system/README.md +6 -0
- package/synthetizer/worklet_system/main_processor.js +363 -0
- package/synthetizer/worklet_system/message_protocol/handle_message.js +193 -0
- package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +118 -0
- package/synthetizer/worklet_system/minify_processor.sh +4 -0
- package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
- package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
- package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
- package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
- package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
- package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
- package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
- package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
- package/synthetizer/worklet_system/worklet_processor.js +9 -0
- package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
- package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
- package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
- package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
- package/synthetizer/worklet_url.js +5 -0
- package/utils/README.md +4 -0
- package/utils/buffer_to_wav.js +101 -0
- package/utils/byte_functions/big_endian.js +28 -0
- package/utils/byte_functions/little_endian.js +74 -0
- package/utils/byte_functions/string.js +97 -0
- package/utils/byte_functions/variable_length_quantity.js +37 -0
- package/utils/encode_vorbis.js +30 -0
- package/utils/indexed_array.js +41 -0
- package/utils/loggin.js +79 -0
- package/utils/other.js +54 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {RiffChunk} from "./riff_chunk.js";
|
|
2
|
+
import {InstrumentZone} from "./zones.js";
|
|
3
|
+
import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* instrument.js
|
|
8
|
+
* purpose: parses soundfont instrument and stores them as a class
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class Instrument
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* Creates an instrument
|
|
15
|
+
* @param instrumentChunk {RiffChunk}
|
|
16
|
+
*/
|
|
17
|
+
constructor(instrumentChunk)
|
|
18
|
+
{
|
|
19
|
+
this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20).trim();
|
|
20
|
+
this.instrumentZoneIndex = readBytesAsUintLittleEndian(instrumentChunk.chunkData, 2);
|
|
21
|
+
this.instrumentZonesAmount = 0;
|
|
22
|
+
/**
|
|
23
|
+
* @type {InstrumentZone[]}
|
|
24
|
+
*/
|
|
25
|
+
this.instrumentZones = [];
|
|
26
|
+
this._useCount = 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addUseCount()
|
|
30
|
+
{
|
|
31
|
+
this._useCount++;
|
|
32
|
+
this.instrumentZones.forEach(z => z.useCount++);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
removeUseCount()
|
|
36
|
+
{
|
|
37
|
+
this._useCount--;
|
|
38
|
+
for(let i = 0; i < this.instrumentZones.length; i++)
|
|
39
|
+
{
|
|
40
|
+
if(this.safeDeleteZone(i))
|
|
41
|
+
{
|
|
42
|
+
i--;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
get useCount()
|
|
51
|
+
{
|
|
52
|
+
return this._useCount;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
deleteInstrument()
|
|
56
|
+
{
|
|
57
|
+
this.instrumentZones.forEach(z => z.deleteZone());
|
|
58
|
+
this.instrumentZones.length = 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param index {number}
|
|
63
|
+
* @returns {boolean} is the zone has been deleted
|
|
64
|
+
*/
|
|
65
|
+
safeDeleteZone(index)
|
|
66
|
+
{
|
|
67
|
+
this.instrumentZones[index].useCount--;
|
|
68
|
+
if(this.instrumentZones[index].useCount < 1)
|
|
69
|
+
{
|
|
70
|
+
this.deleteZone(index);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param index {number}
|
|
78
|
+
*/
|
|
79
|
+
deleteZone(index)
|
|
80
|
+
{
|
|
81
|
+
this.instrumentZones[index].deleteZone();
|
|
82
|
+
this.instrumentZones.splice(index, 1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Loads all the instrument zones, given the amount
|
|
87
|
+
* @param amount {number}
|
|
88
|
+
* @param zones {InstrumentZone[]}
|
|
89
|
+
*/
|
|
90
|
+
getInstrumentZones(amount, zones)
|
|
91
|
+
{
|
|
92
|
+
this.instrumentZonesAmount = amount;
|
|
93
|
+
for(let i = this.instrumentZoneIndex; i < this.instrumentZonesAmount + this.instrumentZoneIndex; i++)
|
|
94
|
+
{
|
|
95
|
+
this.instrumentZones.push(zones[i]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reads the instruments
|
|
102
|
+
* @param instrumentChunk {RiffChunk}
|
|
103
|
+
* @param instrumentZones {InstrumentZone[]}
|
|
104
|
+
* @returns {Instrument[]}
|
|
105
|
+
*/
|
|
106
|
+
export function readInstruments(instrumentChunk, instrumentZones)
|
|
107
|
+
{
|
|
108
|
+
let instruments = [];
|
|
109
|
+
while(instrumentChunk.chunkData.length > instrumentChunk.chunkData.currentIndex)
|
|
110
|
+
{
|
|
111
|
+
let instrument = new Instrument(instrumentChunk);
|
|
112
|
+
if(instruments.length > 0)
|
|
113
|
+
{
|
|
114
|
+
let instrumentsAmount = instrument.instrumentZoneIndex - instruments[instruments.length - 1].instrumentZoneIndex;
|
|
115
|
+
instruments[instruments.length - 1].getInstrumentZones(instrumentsAmount, instrumentZones);
|
|
116
|
+
}
|
|
117
|
+
instruments.push(instrument);
|
|
118
|
+
}
|
|
119
|
+
if(instruments.length > 1)
|
|
120
|
+
{
|
|
121
|
+
// remove EOI
|
|
122
|
+
instruments.pop();
|
|
123
|
+
}
|
|
124
|
+
return instruments;
|
|
125
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {signedInt16, readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
2
|
+
import { IndexedByteArray } from '../../utils/indexed_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 {IndexedByteArray|{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
|
+
/**
|
|
48
|
+
* @type {generatorTypes}
|
|
49
|
+
*/
|
|
50
|
+
this.modulatorDestination = dataArray.dest;
|
|
51
|
+
this.modulationSecondarySrc = dataArray.secSrcEnum;
|
|
52
|
+
this.transformAmount = dataArray.amt;
|
|
53
|
+
this.transformType = dataArray.transform;
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
{
|
|
57
|
+
this.modulatorSource = readBytesAsUintLittleEndian(dataArray, 2);
|
|
58
|
+
this.modulatorDestination = readBytesAsUintLittleEndian(dataArray, 2);
|
|
59
|
+
this.transformAmount = signedInt16(dataArray[dataArray.currentIndex++], dataArray[dataArray.currentIndex++]);
|
|
60
|
+
this.modulationSecondarySrc = readBytesAsUintLittleEndian(dataArray, 2);
|
|
61
|
+
this.transformType = readBytesAsUintLittleEndian(dataArray, 2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if(this.modulatorDestination > 58)
|
|
65
|
+
{
|
|
66
|
+
this.modulatorDestination = -1; // flag as invalid (for linked ones)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// decode the source
|
|
70
|
+
this.sourcePolarity = this.modulatorSource >> 9 & 1;
|
|
71
|
+
this.sourceDirection = this.modulatorSource >> 8 & 1;
|
|
72
|
+
this.sourceUsesCC = this.modulatorSource >> 7 & 1;
|
|
73
|
+
this.sourceIndex = this.modulatorSource & 127;
|
|
74
|
+
this.sourceCurveType = this.modulatorSource >> 10 & 3;
|
|
75
|
+
|
|
76
|
+
// decode the secondary source
|
|
77
|
+
this.secSrcPolarity = this.modulationSecondarySrc >> 9 & 1;
|
|
78
|
+
this.secSrcDirection = this.modulationSecondarySrc >> 8 & 1;
|
|
79
|
+
this.secSrcUsesCC = this.modulationSecondarySrc >> 7 & 1;
|
|
80
|
+
this.secSrcIndex = this.modulationSecondarySrc & 127;
|
|
81
|
+
this.secSrcCurveType = this.modulationSecondarySrc >> 10 & 3;
|
|
82
|
+
|
|
83
|
+
//this.precomputeModulatorTransform();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Sums transform and creates a NEW modulator
|
|
88
|
+
* @param modulator {Modulator}
|
|
89
|
+
* @returns {Modulator}
|
|
90
|
+
*/
|
|
91
|
+
sumTransform(modulator)
|
|
92
|
+
{
|
|
93
|
+
return new Modulator({
|
|
94
|
+
srcEnum: this.modulatorSource,
|
|
95
|
+
secSrcEnum: this.modulationSecondarySrc,
|
|
96
|
+
dest: this.modulatorDestination,
|
|
97
|
+
transform: this.transformType,
|
|
98
|
+
amt: this.transformAmount + modulator.transformAmount
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @returns {string}
|
|
104
|
+
*/
|
|
105
|
+
debugString()
|
|
106
|
+
{
|
|
107
|
+
function getKeyByValue(object, value)
|
|
108
|
+
{
|
|
109
|
+
return Object.keys(object).find(key => object[key] === value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let sourceString = getKeyByValue(modulatorCurveTypes, this.sourceCurveType);
|
|
113
|
+
sourceString += this.sourcePolarity === 0 ? " unipolar " : " bipolar ";
|
|
114
|
+
sourceString += this.sourceDirection === 0 ? "forwards " : "backwards ";
|
|
115
|
+
if(this.sourceUsesCC)
|
|
116
|
+
{
|
|
117
|
+
sourceString += getKeyByValue(midiControllers, this.sourceIndex);
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
{
|
|
121
|
+
sourceString += getKeyByValue(modulatorSources, this.sourceIndex);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let secSrcString = getKeyByValue(modulatorCurveTypes, this.secSrcCurveType);
|
|
125
|
+
secSrcString += this.secSrcPolarity === 0 ? " unipolar " : " bipolar ";
|
|
126
|
+
secSrcString += this.secSrcCurveType === 0 ? "forwards " : "backwards ";
|
|
127
|
+
if(this.secSrcUsesCC)
|
|
128
|
+
{
|
|
129
|
+
secSrcString += getKeyByValue(midiControllers, this.secSrcIndex);
|
|
130
|
+
}
|
|
131
|
+
else
|
|
132
|
+
{
|
|
133
|
+
secSrcString += getKeyByValue(modulatorSources, this.secSrcIndex);
|
|
134
|
+
}
|
|
135
|
+
return `Modulator:
|
|
136
|
+
Source: ${sourceString}
|
|
137
|
+
Secondary source: ${secSrcString}
|
|
138
|
+
Destination: ${getKeyByValue(generatorTypes, this.modulatorDestination)}
|
|
139
|
+
Trasform amount: ${this.transformAmount}
|
|
140
|
+
Transform type: ${this.transformType}
|
|
141
|
+
\n\n`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getModSourceEnum(curveType, polarity, direction, isCC, index)
|
|
146
|
+
{
|
|
147
|
+
return (curveType << 10) | (polarity << 9) | (direction << 8) | (isCC << 7) | index;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const DEFAULT_ATTENUATION_MOD_AMOUNT = 960;
|
|
151
|
+
const DEFAULT_ATTENUATION_MOD_CURVE_TYPE = modulatorCurveTypes.concave;
|
|
152
|
+
|
|
153
|
+
export const defaultModulators = [
|
|
154
|
+
// vel to attenuation
|
|
155
|
+
new Modulator({
|
|
156
|
+
srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 0, modulatorSources.noteOnVelocity),
|
|
157
|
+
dest: generatorTypes.initialAttenuation,
|
|
158
|
+
amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
|
|
159
|
+
secSrcEnum: 0x0,
|
|
160
|
+
transform: 0}),
|
|
161
|
+
|
|
162
|
+
// mod wheel to vibrato
|
|
163
|
+
new Modulator({srcEnum: 0x0081, dest: generatorTypes.vibLfoToPitch, amt: 50, secSrcEnum: 0x0, transform: 0}),
|
|
164
|
+
|
|
165
|
+
// vol to attenuation
|
|
166
|
+
new Modulator({
|
|
167
|
+
srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 1, midiControllers.mainVolume),
|
|
168
|
+
dest: generatorTypes.initialAttenuation,
|
|
169
|
+
amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
|
|
170
|
+
secSrcEnum: 0x0,
|
|
171
|
+
transform: 0}),
|
|
172
|
+
|
|
173
|
+
// channel pressure to vibrato
|
|
174
|
+
new Modulator({srcEnum: 0x000D, dest: generatorTypes.vibLfoToPitch, amt: 50, secSrcEnum: 0x0, transform: 0}),
|
|
175
|
+
|
|
176
|
+
// pitch wheel to tuning
|
|
177
|
+
new Modulator({srcEnum: 0x020E, dest: generatorTypes.fineTune, amt: 12700, secSrcEnum: 0x0010, transform: 0}),
|
|
178
|
+
|
|
179
|
+
// pan to uhh, pan
|
|
180
|
+
new Modulator({srcEnum: 0x028A, dest: generatorTypes.pan, amt: 1000, secSrcEnum: 0x0, transform: 0}),
|
|
181
|
+
|
|
182
|
+
// expression to attenuation
|
|
183
|
+
new Modulator({
|
|
184
|
+
srcEnum: getModSourceEnum(DEFAULT_ATTENUATION_MOD_CURVE_TYPE, 0, 1, 1, midiControllers.expressionController),
|
|
185
|
+
dest: generatorTypes.initialAttenuation,
|
|
186
|
+
amt: DEFAULT_ATTENUATION_MOD_AMOUNT,
|
|
187
|
+
secSrcEnum: 0x0,
|
|
188
|
+
transform: 0
|
|
189
|
+
}),
|
|
190
|
+
|
|
191
|
+
// reverb effects to send
|
|
192
|
+
// 1000 to align with the reverbSend (overriding it works anyways)
|
|
193
|
+
new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
|
|
194
|
+
|
|
195
|
+
// chorus effects to send
|
|
196
|
+
new Modulator({srcEnum: 0x00DD, dest: generatorTypes.chorusEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
|
|
197
|
+
|
|
198
|
+
// custom modulators heck yeah
|
|
199
|
+
// poly pressure to vibrato
|
|
200
|
+
new Modulator({
|
|
201
|
+
srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 0, 0, 0, modulatorSources.polyPressure),
|
|
202
|
+
dest: generatorTypes.vibLfoToPitch,
|
|
203
|
+
amt: 50,
|
|
204
|
+
secSrcEnum: 0x0,
|
|
205
|
+
transform: 0
|
|
206
|
+
}),
|
|
207
|
+
|
|
208
|
+
// cc 92 (tremolo) to modLFO volume
|
|
209
|
+
new Modulator({
|
|
210
|
+
srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 0, 0, 1, midiControllers.effects2Depth), /*linear forward unipolar cc 92 */
|
|
211
|
+
dest: generatorTypes.modLfoToVolume,
|
|
212
|
+
amt: 24,
|
|
213
|
+
secSrcEnum: 0x0, // no controller
|
|
214
|
+
transform: 0
|
|
215
|
+
}),
|
|
216
|
+
|
|
217
|
+
// cc 72 (release time) to volEnv release
|
|
218
|
+
new Modulator({
|
|
219
|
+
srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0, 1, midiControllers.releaseTime), // linear forward bipolar cc 72
|
|
220
|
+
dest: generatorTypes.releaseVolEnv,
|
|
221
|
+
amt: 1200,
|
|
222
|
+
secSrcEnum: 0x0, // no controller
|
|
223
|
+
transform: 0
|
|
224
|
+
}),
|
|
225
|
+
|
|
226
|
+
// cc 74 (brightness) to filterFc
|
|
227
|
+
new Modulator({
|
|
228
|
+
srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0 , 1, midiControllers.brightness), // linear forwards bipolar cc 74
|
|
229
|
+
dest: generatorTypes.initialFilterFc,
|
|
230
|
+
amt: 4000,
|
|
231
|
+
secSrcEnum: 0x0, // no controller
|
|
232
|
+
transform: 0
|
|
233
|
+
})
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Reads the modulator read
|
|
238
|
+
* @param modulatorChunk {RiffChunk}
|
|
239
|
+
* @returns {Modulator[]}
|
|
240
|
+
*/
|
|
241
|
+
export function readModulators(modulatorChunk)
|
|
242
|
+
{
|
|
243
|
+
let gens = [];
|
|
244
|
+
while(modulatorChunk.chunkData.length > modulatorChunk.chunkData.currentIndex)
|
|
245
|
+
{
|
|
246
|
+
gens.push(new Modulator(modulatorChunk.chunkData));
|
|
247
|
+
}
|
|
248
|
+
return gens;
|
|
249
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import {RiffChunk} from "./riff_chunk.js";
|
|
2
|
+
import {PresetZone} from "./zones.js";
|
|
3
|
+
import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import {Sample} from "./samples.js";
|
|
5
|
+
import { Generator, generatorTypes } from './generators.js'
|
|
6
|
+
import { defaultModulators } from './modulators.js'
|
|
7
|
+
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* parses soundfont presets, also includes function for getting the generators and samples from midi note and velocity
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class Preset {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a preset
|
|
16
|
+
* @param presetChunk {RiffChunk}
|
|
17
|
+
*/
|
|
18
|
+
constructor(presetChunk)
|
|
19
|
+
{
|
|
20
|
+
this.presetName = readBytesAsString(presetChunk.chunkData, 20)
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/\d{3}:\d{3}/, ""); // remove those pesky "000:001"
|
|
23
|
+
|
|
24
|
+
this.program = readBytesAsUintLittleEndian(presetChunk.chunkData, 2);
|
|
25
|
+
this.bank = readBytesAsUintLittleEndian(presetChunk.chunkData, 2);
|
|
26
|
+
this.presetZoneStartIndex = readBytesAsUintLittleEndian(presetChunk.chunkData, 2);
|
|
27
|
+
this.presetZonesAmount = 0;
|
|
28
|
+
/**
|
|
29
|
+
* @type {PresetZone[]}
|
|
30
|
+
*/
|
|
31
|
+
this.presetZones = [];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stores already found getSamplesAndGenerators for reuse
|
|
35
|
+
* @type {SampleAndGenerators[][][]}
|
|
36
|
+
*/
|
|
37
|
+
this.foundSamplesAndGenerators = [];
|
|
38
|
+
for(let i = 0; i < 128; i++)
|
|
39
|
+
{
|
|
40
|
+
this.foundSamplesAndGenerators[i] = [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// read the dwords
|
|
44
|
+
this.library = readBytesAsUintLittleEndian(presetChunk.chunkData, 4);
|
|
45
|
+
this.genre = readBytesAsUintLittleEndian(presetChunk.chunkData, 4);
|
|
46
|
+
this.morphology = readBytesAsUintLittleEndian(presetChunk.chunkData, 4);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Loads all the preset zones, given the amount
|
|
51
|
+
* @param amount {number}
|
|
52
|
+
* @param zones {PresetZone[]}
|
|
53
|
+
*/
|
|
54
|
+
getPresetZones(amount, zones)
|
|
55
|
+
{
|
|
56
|
+
this.presetZonesAmount = amount;
|
|
57
|
+
for (let i = this.presetZoneStartIndex; i < this.presetZonesAmount + this.presetZoneStartIndex; i++)
|
|
58
|
+
{
|
|
59
|
+
this.presetZones.push(zones[i]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
deletePreset()
|
|
64
|
+
{
|
|
65
|
+
this.presetZones.forEach(z => z.deleteZone());
|
|
66
|
+
this.presetZones.length = 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param index {number}
|
|
71
|
+
*/
|
|
72
|
+
deleteZone(index)
|
|
73
|
+
{
|
|
74
|
+
this.presetZones[index].deleteZone();
|
|
75
|
+
this.presetZones.splice(index, 1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Preloads all samples (async)
|
|
80
|
+
*/
|
|
81
|
+
preload(keyMin, keyMax)
|
|
82
|
+
{
|
|
83
|
+
for (let key = keyMin; key < keyMax + 1; key++)
|
|
84
|
+
{
|
|
85
|
+
for (let velocity = 0; velocity < 128; velocity++)
|
|
86
|
+
{
|
|
87
|
+
this.getSamplesAndGenerators(key, velocity).forEach(samandgen => {
|
|
88
|
+
if(!samandgen.sample.isSampleLoaded)
|
|
89
|
+
{
|
|
90
|
+
samandgen.sample.getAudioData();
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Preloads a specific key/velocity combo
|
|
99
|
+
* @param key {number}
|
|
100
|
+
* @param velocity {number}
|
|
101
|
+
*/
|
|
102
|
+
preloadSpecific(key, velocity)
|
|
103
|
+
{
|
|
104
|
+
this.getSamplesAndGenerators(key, velocity).forEach(samandgen => {
|
|
105
|
+
if(!samandgen.sample.isSampleLoaded)
|
|
106
|
+
{
|
|
107
|
+
samandgen.sample.getAudioData();
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {{
|
|
114
|
+
* instrumentGenerators: Generator[],
|
|
115
|
+
* presetGenerators: Generator[],
|
|
116
|
+
* modulators: Modulator[],
|
|
117
|
+
* sample: Sample,
|
|
118
|
+
* sampleID: number,
|
|
119
|
+
* }} SampleAndGenerators
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns generatorTranslator and generators for given note
|
|
124
|
+
* @param midiNote {number}
|
|
125
|
+
* @param velocity {number}
|
|
126
|
+
* @returns {SampleAndGenerators[]}
|
|
127
|
+
*/
|
|
128
|
+
getSamplesAndGenerators(midiNote, velocity)
|
|
129
|
+
{
|
|
130
|
+
const memorized = this.foundSamplesAndGenerators[midiNote][velocity];
|
|
131
|
+
if(memorized)
|
|
132
|
+
{
|
|
133
|
+
return memorized;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isInRange(min, max, number) {
|
|
137
|
+
return number >= min && number <= max;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param mod1 {Modulator}
|
|
142
|
+
* @param mod2 {Modulator}
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
function identicalMod(mod1, mod2)
|
|
146
|
+
{
|
|
147
|
+
return (mod1.modulatorSource === mod2.modulatorSource)
|
|
148
|
+
&& (mod1.modulatorDestination === mod2.modulatorDestination)
|
|
149
|
+
&& (mod1.modulationSecondarySrc === mod2.modulationSecondarySrc)
|
|
150
|
+
&& (mod1.transformType === mod2.transformType);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param main {Generator[]}
|
|
155
|
+
* @param adder {Generator[]}
|
|
156
|
+
*/
|
|
157
|
+
function addUnique(main, adder)
|
|
158
|
+
{
|
|
159
|
+
main.push(...adder.filter(g => !main.find(mg => mg.generatorType === g.generatorType)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param main {Modulator[]}
|
|
164
|
+
* @param adder {Modulator[]}
|
|
165
|
+
*/
|
|
166
|
+
function addUniqueMods(main, adder)
|
|
167
|
+
{
|
|
168
|
+
main.push(...adder.filter(m => !main.find(mm => identicalMod(m, mm))));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @type {SampleAndGenerators[]}
|
|
173
|
+
*/
|
|
174
|
+
let parsedGeneratorsAndSamples = [];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* global zone is always first, so it or nothing
|
|
178
|
+
* @type {Generator[]}
|
|
179
|
+
*/
|
|
180
|
+
let globalPresetGenerators = this.presetZones[0].isGlobal ? [...this.presetZones[0].generators] : [];
|
|
181
|
+
|
|
182
|
+
let globalPresetModulators = this.presetZones[0].isGlobal ? [...this.presetZones[0].modulators] : [];
|
|
183
|
+
|
|
184
|
+
// find the preset zones in range
|
|
185
|
+
let presetZonesInRange = this.presetZones.filter(currentZone =>
|
|
186
|
+
(
|
|
187
|
+
isInRange(currentZone.keyRange.min, currentZone.keyRange.max, midiNote)
|
|
188
|
+
&&
|
|
189
|
+
isInRange(currentZone.velRange.min, currentZone.velRange.max, velocity)
|
|
190
|
+
) && !currentZone.isGlobal);
|
|
191
|
+
|
|
192
|
+
presetZonesInRange.forEach(zone =>
|
|
193
|
+
{
|
|
194
|
+
let presetGenerators = zone.generators;
|
|
195
|
+
let presetModulators = zone.modulators;
|
|
196
|
+
/**
|
|
197
|
+
* global zone is always first, so it or nothing
|
|
198
|
+
* @type {Generator[]}
|
|
199
|
+
*/
|
|
200
|
+
let globalInstrumentGenerators = zone.instrument.instrumentZones[0].isGlobal ? [...zone.instrument.instrumentZones[0].generators] : [];
|
|
201
|
+
let globalInstrumentModulators = zone.instrument.instrumentZones[0].isGlobal ? [...zone.instrument.instrumentZones[0].modulators] : [];
|
|
202
|
+
|
|
203
|
+
let instrumentZonesInRange = zone.instrument.instrumentZones
|
|
204
|
+
.filter(currentZone =>
|
|
205
|
+
(
|
|
206
|
+
isInRange(currentZone.keyRange.min,
|
|
207
|
+
currentZone.keyRange.max,
|
|
208
|
+
midiNote)
|
|
209
|
+
&&
|
|
210
|
+
isInRange(currentZone.velRange.min,
|
|
211
|
+
currentZone.velRange.max,
|
|
212
|
+
velocity)
|
|
213
|
+
) && !currentZone.isGlobal
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
instrumentZonesInRange.forEach(instrumentZone =>
|
|
217
|
+
{
|
|
218
|
+
let instrumentGenerators = [...instrumentZone.generators];
|
|
219
|
+
let instrumentModulators = [...instrumentZone.modulators];
|
|
220
|
+
|
|
221
|
+
addUnique(presetGenerators, globalPresetGenerators);
|
|
222
|
+
// add the unique global preset generators (local replace global(
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
// add the unique global instrument generators (local replace global)
|
|
226
|
+
addUnique(instrumentGenerators, globalInstrumentGenerators);
|
|
227
|
+
|
|
228
|
+
addUniqueMods(presetModulators, globalPresetModulators);
|
|
229
|
+
addUniqueMods(instrumentModulators, globalInstrumentModulators);
|
|
230
|
+
|
|
231
|
+
// default mods
|
|
232
|
+
addUniqueMods(instrumentModulators, defaultModulators);
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* sum preset modulators to instruments (amount) sf spec page 54
|
|
236
|
+
* @type {Modulator[]}
|
|
237
|
+
*/
|
|
238
|
+
const finalModulatorList = [...instrumentModulators];
|
|
239
|
+
for(let i = 0; i < presetModulators.length; i++)
|
|
240
|
+
{
|
|
241
|
+
let mod = presetModulators[i];
|
|
242
|
+
const identicalInstrumentModulator = finalModulatorList.findIndex(m => identicalMod(mod, m));
|
|
243
|
+
if(identicalInstrumentModulator !== -1)
|
|
244
|
+
{
|
|
245
|
+
// sum the amounts (this makes a new modulator because otherwise it would overwrite the one in the soundfont!!!
|
|
246
|
+
finalModulatorList[identicalInstrumentModulator] = finalModulatorList[identicalInstrumentModulator].sumTransform(mod);
|
|
247
|
+
}
|
|
248
|
+
else
|
|
249
|
+
{
|
|
250
|
+
finalModulatorList.push(mod);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
// combine both generators and add to the final result
|
|
256
|
+
parsedGeneratorsAndSamples.push({
|
|
257
|
+
instrumentGenerators: instrumentGenerators,
|
|
258
|
+
presetGenerators: presetGenerators,
|
|
259
|
+
modulators: finalModulatorList,
|
|
260
|
+
sample: instrumentZone.sample,
|
|
261
|
+
sampleID: instrumentZone.generators.find(g => g.generatorType === generatorTypes.sampleID).generatorValue
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// save and return
|
|
267
|
+
this.foundSamplesAndGenerators[midiNote][velocity] = parsedGeneratorsAndSamples;
|
|
268
|
+
return parsedGeneratorsAndSamples;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Reads the presets
|
|
274
|
+
* @param presetChunk {RiffChunk}
|
|
275
|
+
* @param presetZones {PresetZone[]}
|
|
276
|
+
* @returns {Preset[]}
|
|
277
|
+
*/
|
|
278
|
+
export function readPresets(presetChunk, presetZones)
|
|
279
|
+
{
|
|
280
|
+
/**
|
|
281
|
+
* @type {Preset[]}
|
|
282
|
+
*/
|
|
283
|
+
let presets = [];
|
|
284
|
+
while(presetChunk.chunkData.length > presetChunk.chunkData.currentIndex)
|
|
285
|
+
{
|
|
286
|
+
let preset = new Preset(presetChunk);
|
|
287
|
+
if(presets.length > 0)
|
|
288
|
+
{
|
|
289
|
+
let presetZonesAmount = preset.presetZoneStartIndex - presets[presets.length - 1].presetZoneStartIndex;
|
|
290
|
+
presets[presets.length - 1].getPresetZones(presetZonesAmount, presetZones);
|
|
291
|
+
}
|
|
292
|
+
presets.push(preset);
|
|
293
|
+
}
|
|
294
|
+
// remove EOP
|
|
295
|
+
if (presets.length > 1)
|
|
296
|
+
{
|
|
297
|
+
presets.pop();
|
|
298
|
+
}
|
|
299
|
+
return presets;
|
|
300
|
+
}
|